Also adjudicate after user move
[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 void
2068 read_from_ics(isr, closure, data, count, error)
2069      InputSourceRef isr;
2070      VOIDSTAR closure;
2071      char *data;
2072      int count;
2073      int error;
2074 {
2075 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2076 #define STARTED_NONE 0
2077 #define STARTED_MOVES 1
2078 #define STARTED_BOARD 2
2079 #define STARTED_OBSERVE 3
2080 #define STARTED_HOLDINGS 4
2081 #define STARTED_CHATTER 5
2082 #define STARTED_COMMENT 6
2083 #define STARTED_MOVES_NOHIDE 7
2084     
2085     static int started = STARTED_NONE;
2086     static char parse[20000];
2087     static int parse_pos = 0;
2088     static char buf[BUF_SIZE + 1];
2089     static int firstTime = TRUE, intfSet = FALSE;
2090     static ColorClass prevColor = ColorNormal;
2091     static int savingComment = FALSE;
2092     static int cmatch = 0; // continuation sequence match
2093     char *bp;
2094     char str[500];
2095     int i, oldi;
2096     int buf_len;
2097     int next_out;
2098     int tkind;
2099     int backup;    /* [DM] For zippy color lines */
2100     char *p;
2101     char talker[MSG_SIZ]; // [HGM] chat
2102     int channel;
2103
2104     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105
2106     if (appData.debugMode) {
2107       if (!error) {
2108         fprintf(debugFP, "<ICS: ");
2109         show_bytes(debugFP, data, count);
2110         fprintf(debugFP, "\n");
2111       }
2112     }
2113
2114     if (appData.debugMode) { int f = forwardMostMove;
2115         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2116                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2117                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2118     }
2119     if (count > 0) {
2120         /* If last read ended with a partial line that we couldn't parse,
2121            prepend it to the new read and try again. */
2122         if (leftover_len > 0) {
2123             for (i=0; i<leftover_len; i++)
2124               buf[i] = buf[leftover_start + i];
2125         }
2126
2127     /* copy new characters into the buffer */
2128     bp = buf + leftover_len;
2129     buf_len=leftover_len;
2130     for (i=0; i<count; i++)
2131     {
2132         // ignore these
2133         if (data[i] == '\r')
2134             continue;
2135
2136         // join lines split by ICS?
2137         if (!appData.noJoin)
2138         {
2139             /*
2140                 Joining just consists of finding matches against the
2141                 continuation sequence, and discarding that sequence
2142                 if found instead of copying it.  So, until a match
2143                 fails, there's nothing to do since it might be the
2144                 complete sequence, and thus, something we don't want
2145                 copied.
2146             */
2147             if (data[i] == cont_seq[cmatch])
2148             {
2149                 cmatch++;
2150                 if (cmatch == strlen(cont_seq))
2151                 {
2152                     cmatch = 0; // complete match.  just reset the counter
2153
2154                     /*
2155                         it's possible for the ICS to not include the space
2156                         at the end of the last word, making our [correct]
2157                         join operation fuse two separate words.  the server
2158                         does this when the space occurs at the width setting.
2159                     */
2160                     if (!buf_len || buf[buf_len-1] != ' ')
2161                     {
2162                         *bp++ = ' ';
2163                         buf_len++;
2164                     }
2165                 }
2166                 continue;
2167             }
2168             else if (cmatch)
2169             {
2170                 /*
2171                     match failed, so we have to copy what matched before
2172                     falling through and copying this character.  In reality,
2173                     this will only ever be just the newline character, but
2174                     it doesn't hurt to be precise.
2175                 */
2176                 strncpy(bp, cont_seq, cmatch);
2177                 bp += cmatch;
2178                 buf_len += cmatch;
2179                 cmatch = 0;
2180             }
2181         }
2182
2183         // copy this char
2184         *bp++ = data[i];
2185         buf_len++;
2186     }
2187
2188         buf[buf_len] = NULLCHAR;
2189 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2190         next_out = 0;
2191         leftover_start = 0;
2192         
2193         i = 0;
2194         while (i < buf_len) {
2195             /* Deal with part of the TELNET option negotiation
2196                protocol.  We refuse to do anything beyond the
2197                defaults, except that we allow the WILL ECHO option,
2198                which ICS uses to turn off password echoing when we are
2199                directly connected to it.  We reject this option
2200                if localLineEditing mode is on (always on in xboard)
2201                and we are talking to port 23, which might be a real
2202                telnet server that will try to keep WILL ECHO on permanently.
2203              */
2204             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2205                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2206                 unsigned char option;
2207                 oldi = i;
2208                 switch ((unsigned char) buf[++i]) {
2209                   case TN_WILL:
2210                     if (appData.debugMode)
2211                       fprintf(debugFP, "\n<WILL ");
2212                     switch (option = (unsigned char) buf[++i]) {
2213                       case TN_ECHO:
2214                         if (appData.debugMode)
2215                           fprintf(debugFP, "ECHO ");
2216                         /* Reply only if this is a change, according
2217                            to the protocol rules. */
2218                         if (remoteEchoOption) break;
2219                         if (appData.localLineEditing &&
2220                             atoi(appData.icsPort) == TN_PORT) {
2221                             TelnetRequest(TN_DONT, TN_ECHO);
2222                         } else {
2223                             EchoOff();
2224                             TelnetRequest(TN_DO, TN_ECHO);
2225                             remoteEchoOption = TRUE;
2226                         }
2227                         break;
2228                       default:
2229                         if (appData.debugMode)
2230                           fprintf(debugFP, "%d ", option);
2231                         /* Whatever this is, we don't want it. */
2232                         TelnetRequest(TN_DONT, option);
2233                         break;
2234                     }
2235                     break;
2236                   case TN_WONT:
2237                     if (appData.debugMode)
2238                       fprintf(debugFP, "\n<WONT ");
2239                     switch (option = (unsigned char) buf[++i]) {
2240                       case TN_ECHO:
2241                         if (appData.debugMode)
2242                           fprintf(debugFP, "ECHO ");
2243                         /* Reply only if this is a change, according
2244                            to the protocol rules. */
2245                         if (!remoteEchoOption) break;
2246                         EchoOn();
2247                         TelnetRequest(TN_DONT, TN_ECHO);
2248                         remoteEchoOption = FALSE;
2249                         break;
2250                       default:
2251                         if (appData.debugMode)
2252                           fprintf(debugFP, "%d ", (unsigned char) option);
2253                         /* Whatever this is, it must already be turned
2254                            off, because we never agree to turn on
2255                            anything non-default, so according to the
2256                            protocol rules, we don't reply. */
2257                         break;
2258                     }
2259                     break;
2260                   case TN_DO:
2261                     if (appData.debugMode)
2262                       fprintf(debugFP, "\n<DO ");
2263                     switch (option = (unsigned char) buf[++i]) {
2264                       default:
2265                         /* Whatever this is, we refuse to do it. */
2266                         if (appData.debugMode)
2267                           fprintf(debugFP, "%d ", option);
2268                         TelnetRequest(TN_WONT, option);
2269                         break;
2270                     }
2271                     break;
2272                   case TN_DONT:
2273                     if (appData.debugMode)
2274                       fprintf(debugFP, "\n<DONT ");
2275                     switch (option = (unsigned char) buf[++i]) {
2276                       default:
2277                         if (appData.debugMode)
2278                           fprintf(debugFP, "%d ", option);
2279                         /* Whatever this is, we are already not doing
2280                            it, because we never agree to do anything
2281                            non-default, so according to the protocol
2282                            rules, we don't reply. */
2283                         break;
2284                     }
2285                     break;
2286                   case TN_IAC:
2287                     if (appData.debugMode)
2288                       fprintf(debugFP, "\n<IAC ");
2289                     /* Doubled IAC; pass it through */
2290                     i--;
2291                     break;
2292                   default:
2293                     if (appData.debugMode)
2294                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2295                     /* Drop all other telnet commands on the floor */
2296                     break;
2297                 }
2298                 if (oldi > next_out)
2299                   SendToPlayer(&buf[next_out], oldi - next_out);
2300                 if (++i > next_out)
2301                   next_out = i;
2302                 continue;
2303             }
2304                 
2305             /* OK, this at least will *usually* work */
2306             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2307                 loggedOn = TRUE;
2308             }
2309             
2310             if (loggedOn && !intfSet) {
2311                 if (ics_type == ICS_ICC) {
2312                   sprintf(str,
2313                           "/set-quietly interface %s\n/set-quietly style 12\n",
2314                           programVersion);
2315                 } else if (ics_type == ICS_CHESSNET) {
2316                   sprintf(str, "/style 12\n");
2317                 } else {
2318                   strcpy(str, "alias $ @\n$set interface ");
2319                   strcat(str, programVersion);
2320                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 #ifdef WIN32
2322                   strcat(str, "$iset nohighlight 1\n");
2323 #endif
2324                   strcat(str, "$iset lock 1\n$style 12\n");
2325                 }
2326                 SendToICS(str);
2327                 NotifyFrontendLogin();
2328                 intfSet = TRUE;
2329             }
2330
2331             if (started == STARTED_COMMENT) {
2332                 /* Accumulate characters in comment */
2333                 parse[parse_pos++] = buf[i];
2334                 if (buf[i] == '\n') {
2335                     parse[parse_pos] = NULLCHAR;
2336                     if(chattingPartner>=0) {
2337                         char mess[MSG_SIZ];
2338                         sprintf(mess, "%s%s", talker, parse);
2339                         OutputChatMessage(chattingPartner, mess);
2340                         chattingPartner = -1;
2341                     } else
2342                     if(!suppressKibitz) // [HGM] kibitz
2343                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2344                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2345                         int nrDigit = 0, nrAlph = 0, j;
2346                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2347                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2348                         parse[parse_pos] = NULLCHAR;
2349                         // try to be smart: if it does not look like search info, it should go to
2350                         // ICS interaction window after all, not to engine-output window.
2351                         for(j=0; j<parse_pos; j++) { // count letters and digits
2352                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2353                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2354                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2355                         }
2356                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2357                             int depth=0; float score;
2358                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2359                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2360                                 pvInfoList[forwardMostMove-1].depth = depth;
2361                                 pvInfoList[forwardMostMove-1].score = 100*score;
2362                             }
2363                             OutputKibitz(suppressKibitz, parse);
2364                             next_out = i+1; // [HGM] suppress printing in ICS window
2365                         } else {
2366                             char tmp[MSG_SIZ];
2367                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2368                             SendToPlayer(tmp, strlen(tmp));
2369                         }
2370                     }
2371                     started = STARTED_NONE;
2372                 } else {
2373                     /* Don't match patterns against characters in comment */
2374                     i++;
2375                     continue;
2376                 }
2377             }
2378             if (started == STARTED_CHATTER) {
2379                 if (buf[i] != '\n') {
2380                     /* Don't match patterns against characters in chatter */
2381                     i++;
2382                     continue;
2383                 }
2384                 started = STARTED_NONE;
2385             }
2386
2387             /* Kludge to deal with rcmd protocol */
2388             if (firstTime && looking_at(buf, &i, "\001*")) {
2389                 DisplayFatalError(&buf[1], 0, 1);
2390                 continue;
2391             } else {
2392                 firstTime = FALSE;
2393             }
2394
2395             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2396                 ics_type = ICS_ICC;
2397                 ics_prefix = "/";
2398                 if (appData.debugMode)
2399                   fprintf(debugFP, "ics_type %d\n", ics_type);
2400                 continue;
2401             }
2402             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2403                 ics_type = ICS_FICS;
2404                 ics_prefix = "$";
2405                 if (appData.debugMode)
2406                   fprintf(debugFP, "ics_type %d\n", ics_type);
2407                 continue;
2408             }
2409             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2410                 ics_type = ICS_CHESSNET;
2411                 ics_prefix = "/";
2412                 if (appData.debugMode)
2413                   fprintf(debugFP, "ics_type %d\n", ics_type);
2414                 continue;
2415             }
2416
2417             if (!loggedOn &&
2418                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2419                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2420                  looking_at(buf, &i, "will be \"*\""))) {
2421               strcpy(ics_handle, star_match[0]);
2422               continue;
2423             }
2424
2425             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426               char buf[MSG_SIZ];
2427               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2428               DisplayIcsInteractionTitle(buf);
2429               have_set_title = TRUE;
2430             }
2431
2432             /* skip finger notes */
2433             if (started == STARTED_NONE &&
2434                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2435                  (buf[i] == '1' && buf[i+1] == '0')) &&
2436                 buf[i+2] == ':' && buf[i+3] == ' ') {
2437               started = STARTED_CHATTER;
2438               i += 3;
2439               continue;
2440             }
2441
2442             /* skip formula vars */
2443             if (started == STARTED_NONE &&
2444                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2445               started = STARTED_CHATTER;
2446               i += 3;
2447               continue;
2448             }
2449
2450             oldi = i;
2451             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2452             if (appData.autoKibitz && started == STARTED_NONE && 
2453                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2454                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2455                 if(looking_at(buf, &i, "* kibitzes: ") &&
2456                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2457                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2458                         suppressKibitz = TRUE;
2459                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2460                                 && (gameMode == IcsPlayingWhite)) ||
2461                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2462                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2463                             started = STARTED_CHATTER; // own kibitz we simply discard
2464                         else {
2465                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2466                             parse_pos = 0; parse[0] = NULLCHAR;
2467                             savingComment = TRUE;
2468                             suppressKibitz = gameMode != IcsObserving ? 2 :
2469                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2470                         } 
2471                         continue;
2472                 } else
2473                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2474                     // suppress the acknowledgements of our own autoKibitz
2475                     SendToPlayer(star_match[0], strlen(star_match[0]));
2476                     looking_at(buf, &i, "*% "); // eat prompt
2477                     next_out = i;
2478                 }
2479             } // [HGM] kibitz: end of patch
2480
2481 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482
2483             // [HGM] chat: intercept tells by users for which we have an open chat window
2484             channel = -1;
2485             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2486                                            looking_at(buf, &i, "* whispers:") ||
2487                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2488                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489                 int p;
2490                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2491                 chattingPartner = -1;
2492
2493                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2494                 for(p=0; p<MAX_CHAT; p++) {
2495                     if(channel == atoi(chatPartner[p])) {
2496                     talker[0] = '['; strcat(talker, "]");
2497                     chattingPartner = p; break;
2498                     }
2499                 } else
2500                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2501                 for(p=0; p<MAX_CHAT; p++) {
2502                     if(!strcmp("WHISPER", chatPartner[p])) {
2503                         talker[0] = '['; strcat(talker, "]");
2504                         chattingPartner = p; break;
2505                     }
2506                 }
2507                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2508                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509                     talker[0] = 0;
2510                     chattingPartner = p; break;
2511                 }
2512                 if(chattingPartner<0) i = oldi; else {
2513                     started = STARTED_COMMENT;
2514                     parse_pos = 0; parse[0] = NULLCHAR;
2515                     savingComment = TRUE;
2516                     suppressKibitz = TRUE;
2517                 }
2518             } // [HGM] chat: end of patch
2519
2520             if (appData.zippyTalk || appData.zippyPlay) {
2521                 /* [DM] Backup address for color zippy lines */
2522                 backup = i;
2523 #if ZIPPY
2524        #ifdef WIN32
2525                if (loggedOn == TRUE)
2526                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2527                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528        #else
2529                 if (ZippyControl(buf, &i) ||
2530                     ZippyConverse(buf, &i) ||
2531                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532                       loggedOn = TRUE;
2533                       if (!appData.colorize) continue;
2534                 }
2535        #endif
2536 #endif
2537             } // [DM] 'else { ' deleted
2538                 if (
2539                     /* Regular tells and says */
2540                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2541                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2542                     looking_at(buf, &i, "* says: ") ||
2543                     /* Don't color "message" or "messages" output */
2544                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2545                     looking_at(buf, &i, "*. * at *:*: ") ||
2546                     looking_at(buf, &i, "--* (*:*): ") ||
2547                     /* Message notifications (same color as tells) */
2548                     looking_at(buf, &i, "* has left a message ") ||
2549                     looking_at(buf, &i, "* just sent you a message:\n") ||
2550                     /* Whispers and kibitzes */
2551                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2552                     looking_at(buf, &i, "* kibitzes: ") ||
2553                     /* Channel tells */
2554                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555
2556                   if (tkind == 1 && strchr(star_match[0], ':')) {
2557                       /* Avoid "tells you:" spoofs in channels */
2558                      tkind = 3;
2559                   }
2560                   if (star_match[0][0] == NULLCHAR ||
2561                       strchr(star_match[0], ' ') ||
2562                       (tkind == 3 && strchr(star_match[1], ' '))) {
2563                     /* Reject bogus matches */
2564                     i = oldi;
2565                   } else {
2566                     if (appData.colorize) {
2567                       if (oldi > next_out) {
2568                         SendToPlayer(&buf[next_out], oldi - next_out);
2569                         next_out = oldi;
2570                       }
2571                       switch (tkind) {
2572                       case 1:
2573                         Colorize(ColorTell, FALSE);
2574                         curColor = ColorTell;
2575                         break;
2576                       case 2:
2577                         Colorize(ColorKibitz, FALSE);
2578                         curColor = ColorKibitz;
2579                         break;
2580                       case 3:
2581                         p = strrchr(star_match[1], '(');
2582                         if (p == NULL) {
2583                           p = star_match[1];
2584                         } else {
2585                           p++;
2586                         }
2587                         if (atoi(p) == 1) {
2588                           Colorize(ColorChannel1, FALSE);
2589                           curColor = ColorChannel1;
2590                         } else {
2591                           Colorize(ColorChannel, FALSE);
2592                           curColor = ColorChannel;
2593                         }
2594                         break;
2595                       case 5:
2596                         curColor = ColorNormal;
2597                         break;
2598                       }
2599                     }
2600                     if (started == STARTED_NONE && appData.autoComment &&
2601                         (gameMode == IcsObserving ||
2602                          gameMode == IcsPlayingWhite ||
2603                          gameMode == IcsPlayingBlack)) {
2604                       parse_pos = i - oldi;
2605                       memcpy(parse, &buf[oldi], parse_pos);
2606                       parse[parse_pos] = NULLCHAR;
2607                       started = STARTED_COMMENT;
2608                       savingComment = TRUE;
2609                     } else {
2610                       started = STARTED_CHATTER;
2611                       savingComment = FALSE;
2612                     }
2613                     loggedOn = TRUE;
2614                     continue;
2615                   }
2616                 }
2617
2618                 if (looking_at(buf, &i, "* s-shouts: ") ||
2619                     looking_at(buf, &i, "* c-shouts: ")) {
2620                     if (appData.colorize) {
2621                         if (oldi > next_out) {
2622                             SendToPlayer(&buf[next_out], oldi - next_out);
2623                             next_out = oldi;
2624                         }
2625                         Colorize(ColorSShout, FALSE);
2626                         curColor = ColorSShout;
2627                     }
2628                     loggedOn = TRUE;
2629                     started = STARTED_CHATTER;
2630                     continue;
2631                 }
2632
2633                 if (looking_at(buf, &i, "--->")) {
2634                     loggedOn = TRUE;
2635                     continue;
2636                 }
2637
2638                 if (looking_at(buf, &i, "* shouts: ") ||
2639                     looking_at(buf, &i, "--> ")) {
2640                     if (appData.colorize) {
2641                         if (oldi > next_out) {
2642                             SendToPlayer(&buf[next_out], oldi - next_out);
2643                             next_out = oldi;
2644                         }
2645                         Colorize(ColorShout, FALSE);
2646                         curColor = ColorShout;
2647                     }
2648                     loggedOn = TRUE;
2649                     started = STARTED_CHATTER;
2650                     continue;
2651                 }
2652
2653                 if (looking_at( buf, &i, "Challenge:")) {
2654                     if (appData.colorize) {
2655                         if (oldi > next_out) {
2656                             SendToPlayer(&buf[next_out], oldi - next_out);
2657                             next_out = oldi;
2658                         }
2659                         Colorize(ColorChallenge, FALSE);
2660                         curColor = ColorChallenge;
2661                     }
2662                     loggedOn = TRUE;
2663                     continue;
2664                 }
2665
2666                 if (looking_at(buf, &i, "* offers you") ||
2667                     looking_at(buf, &i, "* offers to be") ||
2668                     looking_at(buf, &i, "* would like to") ||
2669                     looking_at(buf, &i, "* requests to") ||
2670                     looking_at(buf, &i, "Your opponent offers") ||
2671                     looking_at(buf, &i, "Your opponent requests")) {
2672
2673                     if (appData.colorize) {
2674                         if (oldi > next_out) {
2675                             SendToPlayer(&buf[next_out], oldi - next_out);
2676                             next_out = oldi;
2677                         }
2678                         Colorize(ColorRequest, FALSE);
2679                         curColor = ColorRequest;
2680                     }
2681                     continue;
2682                 }
2683
2684                 if (looking_at(buf, &i, "* (*) seeking")) {
2685                     if (appData.colorize) {
2686                         if (oldi > next_out) {
2687                             SendToPlayer(&buf[next_out], oldi - next_out);
2688                             next_out = oldi;
2689                         }
2690                         Colorize(ColorSeek, FALSE);
2691                         curColor = ColorSeek;
2692                     }
2693                     continue;
2694             }
2695
2696             if (looking_at(buf, &i, "\\   ")) {
2697                 if (prevColor != ColorNormal) {
2698                     if (oldi > next_out) {
2699                         SendToPlayer(&buf[next_out], oldi - next_out);
2700                         next_out = oldi;
2701                     }
2702                     Colorize(prevColor, TRUE);
2703                     curColor = prevColor;
2704                 }
2705                 if (savingComment) {
2706                     parse_pos = i - oldi;
2707                     memcpy(parse, &buf[oldi], parse_pos);
2708                     parse[parse_pos] = NULLCHAR;
2709                     started = STARTED_COMMENT;
2710                 } else {
2711                     started = STARTED_CHATTER;
2712                 }
2713                 continue;
2714             }
2715
2716             if (looking_at(buf, &i, "Black Strength :") ||
2717                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2718                 looking_at(buf, &i, "<10>") ||
2719                 looking_at(buf, &i, "#@#")) {
2720                 /* Wrong board style */
2721                 loggedOn = TRUE;
2722                 SendToICS(ics_prefix);
2723                 SendToICS("set style 12\n");
2724                 SendToICS(ics_prefix);
2725                 SendToICS("refresh\n");
2726                 continue;
2727             }
2728             
2729             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730                 ICSInitScript();
2731                 have_sent_ICS_logon = 1;
2732                 continue;
2733             }
2734               
2735             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2736                 (looking_at(buf, &i, "\n<12> ") ||
2737                  looking_at(buf, &i, "<12> "))) {
2738                 loggedOn = TRUE;
2739                 if (oldi > next_out) {
2740                     SendToPlayer(&buf[next_out], oldi - next_out);
2741                 }
2742                 next_out = i;
2743                 started = STARTED_BOARD;
2744                 parse_pos = 0;
2745                 continue;
2746             }
2747
2748             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2749                 looking_at(buf, &i, "<b1> ")) {
2750                 if (oldi > next_out) {
2751                     SendToPlayer(&buf[next_out], oldi - next_out);
2752                 }
2753                 next_out = i;
2754                 started = STARTED_HOLDINGS;
2755                 parse_pos = 0;
2756                 continue;
2757             }
2758
2759             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760                 loggedOn = TRUE;
2761                 /* Header for a move list -- first line */
2762
2763                 switch (ics_getting_history) {
2764                   case H_FALSE:
2765                     switch (gameMode) {
2766                       case IcsIdle:
2767                       case BeginningOfGame:
2768                         /* User typed "moves" or "oldmoves" while we
2769                            were idle.  Pretend we asked for these
2770                            moves and soak them up so user can step
2771                            through them and/or save them.
2772                            */
2773                         Reset(FALSE, TRUE);
2774                         gameMode = IcsObserving;
2775                         ModeHighlight();
2776                         ics_gamenum = -1;
2777                         ics_getting_history = H_GOT_UNREQ_HEADER;
2778                         break;
2779                       case EditGame: /*?*/
2780                       case EditPosition: /*?*/
2781                         /* Should above feature work in these modes too? */
2782                         /* For now it doesn't */
2783                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2784                         break;
2785                       default:
2786                         ics_getting_history = H_GOT_UNWANTED_HEADER;
2787                         break;
2788                     }
2789                     break;
2790                   case H_REQUESTED:
2791                     /* Is this the right one? */
2792                     if (gameInfo.white && gameInfo.black &&
2793                         strcmp(gameInfo.white, star_match[0]) == 0 &&
2794                         strcmp(gameInfo.black, star_match[2]) == 0) {
2795                         /* All is well */
2796                         ics_getting_history = H_GOT_REQ_HEADER;
2797                     }
2798                     break;
2799                   case H_GOT_REQ_HEADER:
2800                   case H_GOT_UNREQ_HEADER:
2801                   case H_GOT_UNWANTED_HEADER:
2802                   case H_GETTING_MOVES:
2803                     /* Should not happen */
2804                     DisplayError(_("Error gathering move list: two headers"), 0);
2805                     ics_getting_history = H_FALSE;
2806                     break;
2807                 }
2808
2809                 /* Save player ratings into gameInfo if needed */
2810                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2811                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
2812                     (gameInfo.whiteRating == -1 ||
2813                      gameInfo.blackRating == -1)) {
2814
2815                     gameInfo.whiteRating = string_to_rating(star_match[1]);
2816                     gameInfo.blackRating = string_to_rating(star_match[3]);
2817                     if (appData.debugMode)
2818                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
2819                               gameInfo.whiteRating, gameInfo.blackRating);
2820                 }
2821                 continue;
2822             }
2823
2824             if (looking_at(buf, &i,
2825               "* * match, initial time: * minute*, increment: * second")) {
2826                 /* Header for a move list -- second line */
2827                 /* Initial board will follow if this is a wild game */
2828                 if (gameInfo.event != NULL) free(gameInfo.event);
2829                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2830                 gameInfo.event = StrSave(str);
2831                 /* [HGM] we switched variant. Translate boards if needed. */
2832                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2833                 continue;
2834             }
2835
2836             if (looking_at(buf, &i, "Move  ")) {
2837                 /* Beginning of a move list */
2838                 switch (ics_getting_history) {
2839                   case H_FALSE:
2840                     /* Normally should not happen */
2841                     /* Maybe user hit reset while we were parsing */
2842                     break;
2843                   case H_REQUESTED:
2844                     /* Happens if we are ignoring a move list that is not
2845                      * the one we just requested.  Common if the user
2846                      * tries to observe two games without turning off
2847                      * getMoveList */
2848                     break;
2849                   case H_GETTING_MOVES:
2850                     /* Should not happen */
2851                     DisplayError(_("Error gathering move list: nested"), 0);
2852                     ics_getting_history = H_FALSE;
2853                     break;
2854                   case H_GOT_REQ_HEADER:
2855                     ics_getting_history = H_GETTING_MOVES;
2856                     started = STARTED_MOVES;
2857                     parse_pos = 0;
2858                     if (oldi > next_out) {
2859                         SendToPlayer(&buf[next_out], oldi - next_out);
2860                     }
2861                     break;
2862                   case H_GOT_UNREQ_HEADER:
2863                     ics_getting_history = H_GETTING_MOVES;
2864                     started = STARTED_MOVES_NOHIDE;
2865                     parse_pos = 0;
2866                     break;
2867                   case H_GOT_UNWANTED_HEADER:
2868                     ics_getting_history = H_FALSE;
2869                     break;
2870                 }
2871                 continue;
2872             }                           
2873             
2874             if (looking_at(buf, &i, "% ") ||
2875                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2876                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2877                 if(suppressKibitz) next_out = i;
2878                 savingComment = FALSE;
2879                 suppressKibitz = 0;
2880                 switch (started) {
2881                   case STARTED_MOVES:
2882                   case STARTED_MOVES_NOHIDE:
2883                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2884                     parse[parse_pos + i - oldi] = NULLCHAR;
2885                     ParseGameHistory(parse);
2886 #if ZIPPY
2887                     if (appData.zippyPlay && first.initDone) {
2888                         FeedMovesToProgram(&first, forwardMostMove);
2889                         if (gameMode == IcsPlayingWhite) {
2890                             if (WhiteOnMove(forwardMostMove)) {
2891                                 if (first.sendTime) {
2892                                   if (first.useColors) {
2893                                     SendToProgram("black\n", &first); 
2894                                   }
2895                                   SendTimeRemaining(&first, TRUE);
2896                                 }
2897                                 if (first.useColors) {
2898                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899                                 }
2900                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2901                                 first.maybeThinking = TRUE;
2902                             } else {
2903                                 if (first.usePlayother) {
2904                                   if (first.sendTime) {
2905                                     SendTimeRemaining(&first, TRUE);
2906                                   }
2907                                   SendToProgram("playother\n", &first);
2908                                   firstMove = FALSE;
2909                                 } else {
2910                                   firstMove = TRUE;
2911                                 }
2912                             }
2913                         } else if (gameMode == IcsPlayingBlack) {
2914                             if (!WhiteOnMove(forwardMostMove)) {
2915                                 if (first.sendTime) {
2916                                   if (first.useColors) {
2917                                     SendToProgram("white\n", &first);
2918                                   }
2919                                   SendTimeRemaining(&first, FALSE);
2920                                 }
2921                                 if (first.useColors) {
2922                                   SendToProgram("black\n", &first);
2923                                 }
2924                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2925                                 first.maybeThinking = TRUE;
2926                             } else {
2927                                 if (first.usePlayother) {
2928                                   if (first.sendTime) {
2929                                     SendTimeRemaining(&first, FALSE);
2930                                   }
2931                                   SendToProgram("playother\n", &first);
2932                                   firstMove = FALSE;
2933                                 } else {
2934                                   firstMove = TRUE;
2935                                 }
2936                             }
2937                         }                       
2938                     }
2939 #endif
2940                     if (gameMode == IcsObserving && ics_gamenum == -1) {
2941                         /* Moves came from oldmoves or moves command
2942                            while we weren't doing anything else.
2943                            */
2944                         currentMove = forwardMostMove;
2945                         ClearHighlights();/*!!could figure this out*/
2946                         flipView = appData.flipView;
2947                         DrawPosition(TRUE, boards[currentMove]);
2948                         DisplayBothClocks();
2949                         sprintf(str, "%s vs. %s",
2950                                 gameInfo.white, gameInfo.black);
2951                         DisplayTitle(str);
2952                         gameMode = IcsIdle;
2953                     } else {
2954                         /* Moves were history of an active game */
2955                         if (gameInfo.resultDetails != NULL) {
2956                             free(gameInfo.resultDetails);
2957                             gameInfo.resultDetails = NULL;
2958                         }
2959                     }
2960                     HistorySet(parseList, backwardMostMove,
2961                                forwardMostMove, currentMove-1);
2962                     DisplayMove(currentMove - 1);
2963                     if (started == STARTED_MOVES) next_out = i;
2964                     started = STARTED_NONE;
2965                     ics_getting_history = H_FALSE;
2966                     break;
2967
2968                   case STARTED_OBSERVE:
2969                     started = STARTED_NONE;
2970                     SendToICS(ics_prefix);
2971                     SendToICS("refresh\n");
2972                     break;
2973
2974                   default:
2975                     break;
2976                 }
2977                 if(bookHit) { // [HGM] book: simulate book reply
2978                     static char bookMove[MSG_SIZ]; // a bit generous?
2979
2980                     programStats.nodes = programStats.depth = programStats.time = 
2981                     programStats.score = programStats.got_only_move = 0;
2982                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983
2984                     strcpy(bookMove, "move ");
2985                     strcat(bookMove, bookHit);
2986                     HandleMachineMove(bookMove, &first);
2987                 }
2988                 continue;
2989             }
2990             
2991             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2992                  started == STARTED_HOLDINGS ||
2993                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2994                 /* Accumulate characters in move list or board */
2995                 parse[parse_pos++] = buf[i];
2996             }
2997             
2998             /* Start of game messages.  Mostly we detect start of game
2999                when the first board image arrives.  On some versions
3000                of the ICS, though, we need to do a "refresh" after starting
3001                to observe in order to get the current board right away. */
3002             if (looking_at(buf, &i, "Adding game * to observation list")) {
3003                 started = STARTED_OBSERVE;
3004                 continue;
3005             }
3006
3007             /* Handle auto-observe */
3008             if (appData.autoObserve &&
3009                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3010                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011                 char *player;
3012                 /* Choose the player that was highlighted, if any. */
3013                 if (star_match[0][0] == '\033' ||
3014                     star_match[1][0] != '\033') {
3015                     player = star_match[0];
3016                 } else {
3017                     player = star_match[2];
3018                 }
3019                 sprintf(str, "%sobserve %s\n",
3020                         ics_prefix, StripHighlightAndTitle(player));
3021                 SendToICS(str);
3022
3023                 /* Save ratings from notify string */
3024                 strcpy(player1Name, star_match[0]);
3025                 player1Rating = string_to_rating(star_match[1]);
3026                 strcpy(player2Name, star_match[2]);
3027                 player2Rating = string_to_rating(star_match[3]);
3028
3029                 if (appData.debugMode)
3030                   fprintf(debugFP, 
3031                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3032                           player1Name, player1Rating,
3033                           player2Name, player2Rating);
3034
3035                 continue;
3036             }
3037
3038             /* Deal with automatic examine mode after a game,
3039                and with IcsObserving -> IcsExamining transition */
3040             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3041                 looking_at(buf, &i, "has made you an examiner of game *")) {
3042
3043                 int gamenum = atoi(star_match[0]);
3044                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3045                     gamenum == ics_gamenum) {
3046                     /* We were already playing or observing this game;
3047                        no need to refetch history */
3048                     gameMode = IcsExamining;
3049                     if (pausing) {
3050                         pauseExamForwardMostMove = forwardMostMove;
3051                     } else if (currentMove < forwardMostMove) {
3052                         ForwardInner(forwardMostMove);
3053                     }
3054                 } else {
3055                     /* I don't think this case really can happen */
3056                     SendToICS(ics_prefix);
3057                     SendToICS("refresh\n");
3058                 }
3059                 continue;
3060             }    
3061             
3062             /* Error messages */
3063 //          if (ics_user_moved) {
3064             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3065                 if (looking_at(buf, &i, "Illegal move") ||
3066                     looking_at(buf, &i, "Not a legal move") ||
3067                     looking_at(buf, &i, "Your king is in check") ||
3068                     looking_at(buf, &i, "It isn't your turn") ||
3069                     looking_at(buf, &i, "It is not your move")) {
3070                     /* Illegal move */
3071                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3072                         currentMove = --forwardMostMove;
3073                         DisplayMove(currentMove - 1); /* before DMError */
3074                         DrawPosition(FALSE, boards[currentMove]);
3075                         SwitchClocks();
3076                         DisplayBothClocks();
3077                     }
3078                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3079                     ics_user_moved = 0;
3080                     continue;
3081                 }
3082             }
3083
3084             if (looking_at(buf, &i, "still have time") ||
3085                 looking_at(buf, &i, "not out of time") ||
3086                 looking_at(buf, &i, "either player is out of time") ||
3087                 looking_at(buf, &i, "has timeseal; checking")) {
3088                 /* We must have called his flag a little too soon */
3089                 whiteFlag = blackFlag = FALSE;
3090                 continue;
3091             }
3092
3093             if (looking_at(buf, &i, "added * seconds to") ||
3094                 looking_at(buf, &i, "seconds were added to")) {
3095                 /* Update the clocks */
3096                 SendToICS(ics_prefix);
3097                 SendToICS("refresh\n");
3098                 continue;
3099             }
3100
3101             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3102                 ics_clock_paused = TRUE;
3103                 StopClocks();
3104                 continue;
3105             }
3106
3107             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3108                 ics_clock_paused = FALSE;
3109                 StartClocks();
3110                 continue;
3111             }
3112
3113             /* Grab player ratings from the Creating: message.
3114                Note we have to check for the special case when
3115                the ICS inserts things like [white] or [black]. */
3116             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3117                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118                 /* star_matches:
3119                    0    player 1 name (not necessarily white)
3120                    1    player 1 rating
3121                    2    empty, white, or black (IGNORED)
3122                    3    player 2 name (not necessarily black)
3123                    4    player 2 rating
3124                    
3125                    The names/ratings are sorted out when the game
3126                    actually starts (below).
3127                 */
3128                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3129                 player1Rating = string_to_rating(star_match[1]);
3130                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3131                 player2Rating = string_to_rating(star_match[4]);
3132
3133                 if (appData.debugMode)
3134                   fprintf(debugFP, 
3135                           "Ratings from 'Creating:' %s %d, %s %d\n",
3136                           player1Name, player1Rating,
3137                           player2Name, player2Rating);
3138
3139                 continue;
3140             }
3141             
3142             /* Improved generic start/end-of-game messages */
3143             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3144                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3145                 /* If tkind == 0: */
3146                 /* star_match[0] is the game number */
3147                 /*           [1] is the white player's name */
3148                 /*           [2] is the black player's name */
3149                 /* For end-of-game: */
3150                 /*           [3] is the reason for the game end */
3151                 /*           [4] is a PGN end game-token, preceded by " " */
3152                 /* For start-of-game: */
3153                 /*           [3] begins with "Creating" or "Continuing" */
3154                 /*           [4] is " *" or empty (don't care). */
3155                 int gamenum = atoi(star_match[0]);
3156                 char *whitename, *blackname, *why, *endtoken;
3157                 ChessMove endtype = (ChessMove) 0;
3158
3159                 if (tkind == 0) {
3160                   whitename = star_match[1];
3161                   blackname = star_match[2];
3162                   why = star_match[3];
3163                   endtoken = star_match[4];
3164                 } else {
3165                   whitename = star_match[1];
3166                   blackname = star_match[3];
3167                   why = star_match[5];
3168                   endtoken = star_match[6];
3169                 }
3170
3171                 /* Game start messages */
3172                 if (strncmp(why, "Creating ", 9) == 0 ||
3173                     strncmp(why, "Continuing ", 11) == 0) {
3174                     gs_gamenum = gamenum;
3175                     strcpy(gs_kind, strchr(why, ' ') + 1);
3176                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 #if ZIPPY
3178                     if (appData.zippyPlay) {
3179                         ZippyGameStart(whitename, blackname);
3180                     }
3181 #endif /*ZIPPY*/
3182                     continue;
3183                 }
3184
3185                 /* Game end messages */
3186                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3187                     ics_gamenum != gamenum) {
3188                     continue;
3189                 }
3190                 while (endtoken[0] == ' ') endtoken++;
3191                 switch (endtoken[0]) {
3192                   case '*':
3193                   default:
3194                     endtype = GameUnfinished;
3195                     break;
3196                   case '0':
3197                     endtype = BlackWins;
3198                     break;
3199                   case '1':
3200                     if (endtoken[1] == '/')
3201                       endtype = GameIsDrawn;
3202                     else
3203                       endtype = WhiteWins;
3204                     break;
3205                 }
3206                 GameEnds(endtype, why, GE_ICS);
3207 #if ZIPPY
3208                 if (appData.zippyPlay && first.initDone) {
3209                     ZippyGameEnd(endtype, why);
3210                     if (first.pr == NULL) {
3211                       /* Start the next process early so that we'll
3212                          be ready for the next challenge */
3213                       StartChessProgram(&first);
3214                     }
3215                     /* Send "new" early, in case this command takes
3216                        a long time to finish, so that we'll be ready
3217                        for the next challenge. */
3218                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3219                     Reset(TRUE, TRUE);
3220                 }
3221 #endif /*ZIPPY*/
3222                 continue;
3223             }
3224
3225             if (looking_at(buf, &i, "Removing game * from observation") ||
3226                 looking_at(buf, &i, "no longer observing game *") ||
3227                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3228                 if (gameMode == IcsObserving &&
3229                     atoi(star_match[0]) == ics_gamenum)
3230                   {
3231                       /* icsEngineAnalyze */
3232                       if (appData.icsEngineAnalyze) {
3233                             ExitAnalyzeMode();
3234                             ModeHighlight();
3235                       }
3236                       StopClocks();
3237                       gameMode = IcsIdle;
3238                       ics_gamenum = -1;
3239                       ics_user_moved = FALSE;
3240                   }
3241                 continue;
3242             }
3243
3244             if (looking_at(buf, &i, "no longer examining game *")) {
3245                 if (gameMode == IcsExamining &&
3246                     atoi(star_match[0]) == ics_gamenum)
3247                   {
3248                       gameMode = IcsIdle;
3249                       ics_gamenum = -1;
3250                       ics_user_moved = FALSE;
3251                   }
3252                 continue;
3253             }
3254
3255             /* Advance leftover_start past any newlines we find,
3256                so only partial lines can get reparsed */
3257             if (looking_at(buf, &i, "\n")) {
3258                 prevColor = curColor;
3259                 if (curColor != ColorNormal) {
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                         next_out = oldi;
3263                     }
3264                     Colorize(ColorNormal, FALSE);
3265                     curColor = ColorNormal;
3266                 }
3267                 if (started == STARTED_BOARD) {
3268                     started = STARTED_NONE;
3269                     parse[parse_pos] = NULLCHAR;
3270                     ParseBoard12(parse);
3271                     ics_user_moved = 0;
3272
3273                     /* Send premove here */
3274                     if (appData.premove) {
3275                       char str[MSG_SIZ];
3276                       if (currentMove == 0 &&
3277                           gameMode == IcsPlayingWhite &&
3278                           appData.premoveWhite) {
3279                         sprintf(str, "%s\n", appData.premoveWhiteText);
3280                         if (appData.debugMode)
3281                           fprintf(debugFP, "Sending premove:\n");
3282                         SendToICS(str);
3283                       } else if (currentMove == 1 &&
3284                                  gameMode == IcsPlayingBlack &&
3285                                  appData.premoveBlack) {
3286                         sprintf(str, "%s\n", appData.premoveBlackText);
3287                         if (appData.debugMode)
3288                           fprintf(debugFP, "Sending premove:\n");
3289                         SendToICS(str);
3290                       } else if (gotPremove) {
3291                         gotPremove = 0;
3292                         ClearPremoveHighlights();
3293                         if (appData.debugMode)
3294                           fprintf(debugFP, "Sending premove:\n");
3295                           UserMoveEvent(premoveFromX, premoveFromY, 
3296                                         premoveToX, premoveToY, 
3297                                         premovePromoChar);
3298                       }
3299                     }
3300
3301                     /* Usually suppress following prompt */
3302                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3303                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3304                         if (looking_at(buf, &i, "*% ")) {
3305                             savingComment = FALSE;
3306                             suppressKibitz = 0;
3307                         }
3308                     }
3309                     next_out = i;
3310                 } else if (started == STARTED_HOLDINGS) {
3311                     int gamenum;
3312                     char new_piece[MSG_SIZ];
3313                     started = STARTED_NONE;
3314                     parse[parse_pos] = NULLCHAR;
3315                     if (appData.debugMode)
3316                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3317                                                         parse, currentMove);
3318                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3319                         gamenum == ics_gamenum) {
3320                         if (gameInfo.variant == VariantNormal) {
3321                           /* [HGM] We seem to switch variant during a game!
3322                            * Presumably no holdings were displayed, so we have
3323                            * to move the position two files to the right to
3324                            * create room for them!
3325                            */
3326                           VariantClass newVariant;
3327                           switch(gameInfo.boardWidth) { // base guess on board width
3328                                 case 9:  newVariant = VariantShogi; break;
3329                                 case 10: newVariant = VariantGreat; break;
3330                                 default: newVariant = VariantCrazyhouse; break;
3331                           }
3332                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3333                           /* Get a move list just to see the header, which
3334                              will tell us whether this is really bug or zh */
3335                           if (ics_getting_history == H_FALSE) {
3336                             ics_getting_history = H_REQUESTED;
3337                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3338                             SendToICS(str);
3339                           }
3340                         }
3341                         new_piece[0] = NULLCHAR;
3342                         sscanf(parse, "game %d white [%s black [%s <- %s",
3343                                &gamenum, white_holding, black_holding,
3344                                new_piece);
3345                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3346                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3347                         /* [HGM] copy holdings to board holdings area */
3348                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3349                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3350                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 #if ZIPPY
3352                         if (appData.zippyPlay && first.initDone) {
3353                             ZippyHoldings(white_holding, black_holding,
3354                                           new_piece);
3355                         }
3356 #endif /*ZIPPY*/
3357                         if (tinyLayout || smallLayout) {
3358                             char wh[16], bh[16];
3359                             PackHolding(wh, white_holding);
3360                             PackHolding(bh, black_holding);
3361                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3362                                     gameInfo.white, gameInfo.black);
3363                         } else {
3364                             sprintf(str, "%s [%s] vs. %s [%s]",
3365                                     gameInfo.white, white_holding,
3366                                     gameInfo.black, black_holding);
3367                         }
3368
3369                         DrawPosition(FALSE, boards[currentMove]);
3370                         DisplayTitle(str);
3371                     }
3372                     /* Suppress following prompt */
3373                     if (looking_at(buf, &i, "*% ")) {
3374                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3375                         savingComment = FALSE;
3376                         suppressKibitz = 0;
3377                     }
3378                     next_out = i;
3379                 }
3380                 continue;
3381             }
3382
3383             i++;                /* skip unparsed character and loop back */
3384         }
3385         
3386         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3387 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3388 //          SendToPlayer(&buf[next_out], i - next_out);
3389             started != STARTED_HOLDINGS && leftover_start > next_out) {
3390             SendToPlayer(&buf[next_out], leftover_start - next_out);
3391             next_out = i;
3392         }
3393         
3394         leftover_len = buf_len - leftover_start;
3395         /* if buffer ends with something we couldn't parse,
3396            reparse it after appending the next read */
3397         
3398     } else if (count == 0) {
3399         RemoveInputSource(isr);
3400         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401     } else {
3402         DisplayFatalError(_("Error reading from ICS"), error, 1);
3403     }
3404 }
3405
3406
3407 /* Board style 12 looks like this:
3408    
3409    <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
3410    
3411  * The "<12> " is stripped before it gets to this routine.  The two
3412  * trailing 0's (flip state and clock ticking) are later addition, and
3413  * some chess servers may not have them, or may have only the first.
3414  * Additional trailing fields may be added in the future.  
3415  */
3416
3417 #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"
3418
3419 #define RELATION_OBSERVING_PLAYED    0
3420 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3421 #define RELATION_PLAYING_MYMOVE      1
3422 #define RELATION_PLAYING_NOTMYMOVE  -1
3423 #define RELATION_EXAMINING           2
3424 #define RELATION_ISOLATED_BOARD     -3
3425 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3426
3427 void
3428 ParseBoard12(string)
3429      char *string;
3430
3431     GameMode newGameMode;
3432     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3433     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3434     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3435     char to_play, board_chars[200];
3436     char move_str[500], str[500], elapsed_time[500];
3437     char black[32], white[32];
3438     Board board;
3439     int prevMove = currentMove;
3440     int ticking = 2;
3441     ChessMove moveType;
3442     int fromX, fromY, toX, toY;
3443     char promoChar;
3444     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3445     char *bookHit = NULL; // [HGM] book
3446     Boolean weird = FALSE, reqFlag = FALSE;
3447
3448     fromX = fromY = toX = toY = -1;
3449     
3450     newGame = FALSE;
3451
3452     if (appData.debugMode)
3453       fprintf(debugFP, _("Parsing board: %s\n"), string);
3454
3455     move_str[0] = NULLCHAR;
3456     elapsed_time[0] = NULLCHAR;
3457     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458         int  i = 0, j;
3459         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3460             if(string[i] == ' ') { ranks++; files = 0; }
3461             else files++;
3462             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3463             i++;
3464         }
3465         for(j = 0; j <i; j++) board_chars[j] = string[j];
3466         board_chars[i] = '\0';
3467         string += i + 1;
3468     }
3469     n = sscanf(string, PATTERN, &to_play, &double_push,
3470                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3471                &gamenum, white, black, &relation, &basetime, &increment,
3472                &white_stren, &black_stren, &white_time, &black_time,
3473                &moveNum, str, elapsed_time, move_str, &ics_flip,
3474                &ticking);
3475
3476     if (n < 21) {
3477         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3478         DisplayError(str, 0);
3479         return;
3480     }
3481
3482     /* Convert the move number to internal form */
3483     moveNum = (moveNum - 1) * 2;
3484     if (to_play == 'B') moveNum++;
3485     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3486       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3487                         0, 1);
3488       return;
3489     }
3490     
3491     switch (relation) {
3492       case RELATION_OBSERVING_PLAYED:
3493       case RELATION_OBSERVING_STATIC:
3494         if (gamenum == -1) {
3495             /* Old ICC buglet */
3496             relation = RELATION_OBSERVING_STATIC;
3497         }
3498         newGameMode = IcsObserving;
3499         break;
3500       case RELATION_PLAYING_MYMOVE:
3501       case RELATION_PLAYING_NOTMYMOVE:
3502         newGameMode =
3503           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3504             IcsPlayingWhite : IcsPlayingBlack;
3505         break;
3506       case RELATION_EXAMINING:
3507         newGameMode = IcsExamining;
3508         break;
3509       case RELATION_ISOLATED_BOARD:
3510       default:
3511         /* Just display this board.  If user was doing something else,
3512            we will forget about it until the next board comes. */ 
3513         newGameMode = IcsIdle;
3514         break;
3515       case RELATION_STARTING_POSITION:
3516         newGameMode = gameMode;
3517         break;
3518     }
3519     
3520     /* Modify behavior for initial board display on move listing
3521        of wild games.
3522        */
3523     switch (ics_getting_history) {
3524       case H_FALSE:
3525       case H_REQUESTED:
3526         break;
3527       case H_GOT_REQ_HEADER:
3528       case H_GOT_UNREQ_HEADER:
3529         /* This is the initial position of the current game */
3530         gamenum = ics_gamenum;
3531         moveNum = 0;            /* old ICS bug workaround */
3532         if (to_play == 'B') {
3533           startedFromSetupPosition = TRUE;
3534           blackPlaysFirst = TRUE;
3535           moveNum = 1;
3536           if (forwardMostMove == 0) forwardMostMove = 1;
3537           if (backwardMostMove == 0) backwardMostMove = 1;
3538           if (currentMove == 0) currentMove = 1;
3539         }
3540         newGameMode = gameMode;
3541         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542         break;
3543       case H_GOT_UNWANTED_HEADER:
3544         /* This is an initial board that we don't want */
3545         return;
3546       case H_GETTING_MOVES:
3547         /* Should not happen */
3548         DisplayError(_("Error gathering move list: extra board"), 0);
3549         ics_getting_history = H_FALSE;
3550         return;
3551     }
3552
3553    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3554                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3555      /* [HGM] We seem to have switched variant unexpectedly
3556       * Try to guess new variant from board size
3557       */
3558           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3559           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3560           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3561           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3562           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3563           if(!weird) newVariant = VariantNormal;
3564           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3565           /* Get a move list just to see the header, which
3566              will tell us whether this is really bug or zh */
3567           if (ics_getting_history == H_FALSE) {
3568             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3569             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3570             SendToICS(str);
3571           }
3572     }
3573     
3574     /* Take action if this is the first board of a new game, or of a
3575        different game than is currently being displayed.  */
3576     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3577         relation == RELATION_ISOLATED_BOARD) {
3578         
3579         /* Forget the old game and get the history (if any) of the new one */
3580         if (gameMode != BeginningOfGame) {
3581           Reset(TRUE, TRUE);
3582         }
3583         newGame = TRUE;
3584         if (appData.autoRaiseBoard) BoardToTop();
3585         prevMove = -3;
3586         if (gamenum == -1) {
3587             newGameMode = IcsIdle;
3588         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3589                    appData.getMoveList && !reqFlag) {
3590             /* Need to get game history */
3591             ics_getting_history = H_REQUESTED;
3592             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3593             SendToICS(str);
3594         }
3595         
3596         /* Initially flip the board to have black on the bottom if playing
3597            black or if the ICS flip flag is set, but let the user change
3598            it with the Flip View button. */
3599         flipView = appData.autoFlipView ? 
3600           (newGameMode == IcsPlayingBlack) || ics_flip :
3601           appData.flipView;
3602         
3603         /* Done with values from previous mode; copy in new ones */
3604         gameMode = newGameMode;
3605         ModeHighlight();
3606         ics_gamenum = gamenum;
3607         if (gamenum == gs_gamenum) {
3608             int klen = strlen(gs_kind);
3609             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3610             sprintf(str, "ICS %s", gs_kind);
3611             gameInfo.event = StrSave(str);
3612         } else {
3613             gameInfo.event = StrSave("ICS game");
3614         }
3615         gameInfo.site = StrSave(appData.icsHost);
3616         gameInfo.date = PGNDate();
3617         gameInfo.round = StrSave("-");
3618         gameInfo.white = StrSave(white);
3619         gameInfo.black = StrSave(black);
3620         timeControl = basetime * 60 * 1000;
3621         timeControl_2 = 0;
3622         timeIncrement = increment * 1000;
3623         movesPerSession = 0;
3624         gameInfo.timeControl = TimeControlTagValue();
3625         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3626   if (appData.debugMode) {
3627     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3628     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3629     setbuf(debugFP, NULL);
3630   }
3631
3632         gameInfo.outOfBook = NULL;
3633         
3634         /* Do we have the ratings? */
3635         if (strcmp(player1Name, white) == 0 &&
3636             strcmp(player2Name, black) == 0) {
3637             if (appData.debugMode)
3638               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3639                       player1Rating, player2Rating);
3640             gameInfo.whiteRating = player1Rating;
3641             gameInfo.blackRating = player2Rating;
3642         } else if (strcmp(player2Name, white) == 0 &&
3643                    strcmp(player1Name, black) == 0) {
3644             if (appData.debugMode)
3645               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3646                       player2Rating, player1Rating);
3647             gameInfo.whiteRating = player2Rating;
3648             gameInfo.blackRating = player1Rating;
3649         }
3650         player1Name[0] = player2Name[0] = NULLCHAR;
3651
3652         /* Silence shouts if requested */
3653         if (appData.quietPlay &&
3654             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3655             SendToICS(ics_prefix);
3656             SendToICS("set shout 0\n");
3657         }
3658     }
3659     
3660     /* Deal with midgame name changes */
3661     if (!newGame) {
3662         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3663             if (gameInfo.white) free(gameInfo.white);
3664             gameInfo.white = StrSave(white);
3665         }
3666         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3667             if (gameInfo.black) free(gameInfo.black);
3668             gameInfo.black = StrSave(black);
3669         }
3670     }
3671     
3672     /* Throw away game result if anything actually changes in examine mode */
3673     if (gameMode == IcsExamining && !newGame) {
3674         gameInfo.result = GameUnfinished;
3675         if (gameInfo.resultDetails != NULL) {
3676             free(gameInfo.resultDetails);
3677             gameInfo.resultDetails = NULL;
3678         }
3679     }
3680     
3681     /* In pausing && IcsExamining mode, we ignore boards coming
3682        in if they are in a different variation than we are. */
3683     if (pauseExamInvalid) return;
3684     if (pausing && gameMode == IcsExamining) {
3685         if (moveNum <= pauseExamForwardMostMove) {
3686             pauseExamInvalid = TRUE;
3687             forwardMostMove = pauseExamForwardMostMove;
3688             return;
3689         }
3690     }
3691     
3692   if (appData.debugMode) {
3693     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694   }
3695     /* Parse the board */
3696     for (k = 0; k < ranks; k++) {
3697       for (j = 0; j < files; j++)
3698         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3699       if(gameInfo.holdingsWidth > 1) {
3700            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3701            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3702       }
3703     }
3704     CopyBoard(boards[moveNum], board);
3705     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706     if (moveNum == 0) {
3707         startedFromSetupPosition =
3708           !CompareBoards(board, initialPosition);
3709         if(startedFromSetupPosition)
3710             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3711     }
3712
3713     /* [HGM] Set castling rights. Take the outermost Rooks,
3714        to make it also work for FRC opening positions. Note that board12
3715        is really defective for later FRC positions, as it has no way to
3716        indicate which Rook can castle if they are on the same side of King.
3717        For the initial position we grant rights to the outermost Rooks,
3718        and remember thos rights, and we then copy them on positions
3719        later in an FRC game. This means WB might not recognize castlings with
3720        Rooks that have moved back to their original position as illegal,
3721        but in ICS mode that is not its job anyway.
3722     */
3723     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3724     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725
3726         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3727             if(board[0][i] == WhiteRook) j = i;
3728         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3729         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3730             if(board[0][i] == WhiteRook) j = i;
3731         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3732         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3733             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3734         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3735         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3736             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3737         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738
3739         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3740         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3741             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3742         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3743             if(board[BOARD_HEIGHT-1][k] == bKing)
3744                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3745         if(gameInfo.variant == VariantTwoKings) {
3746             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3747             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3748             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3749         }
3750     } else { int r;
3751         r = boards[moveNum][CASTLING][0] = initialRights[0];
3752         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3753         r = boards[moveNum][CASTLING][1] = initialRights[1];
3754         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3755         r = boards[moveNum][CASTLING][3] = initialRights[3];
3756         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3757         r = boards[moveNum][CASTLING][4] = initialRights[4];
3758         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3759         /* wildcastle kludge: always assume King has rights */
3760         r = boards[moveNum][CASTLING][2] = initialRights[2];
3761         r = boards[moveNum][CASTLING][5] = initialRights[5];
3762     }
3763     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3764     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3765
3766     
3767     if (ics_getting_history == H_GOT_REQ_HEADER ||
3768         ics_getting_history == H_GOT_UNREQ_HEADER) {
3769         /* This was an initial position from a move list, not
3770            the current position */
3771         return;
3772     }
3773     
3774     /* Update currentMove and known move number limits */
3775     newMove = newGame || moveNum > forwardMostMove;
3776
3777     if (newGame) {
3778         forwardMostMove = backwardMostMove = currentMove = moveNum;
3779         if (gameMode == IcsExamining && moveNum == 0) {
3780           /* Workaround for ICS limitation: we are not told the wild
3781              type when starting to examine a game.  But if we ask for
3782              the move list, the move list header will tell us */
3783             ics_getting_history = H_REQUESTED;
3784             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3785             SendToICS(str);
3786         }
3787     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3788                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 #if ZIPPY
3790         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3791         /* [HGM] applied this also to an engine that is silently watching        */
3792         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3793             (gameMode == IcsObserving || gameMode == IcsExamining) &&
3794             gameInfo.variant == currentlyInitializedVariant) {
3795           takeback = forwardMostMove - moveNum;
3796           for (i = 0; i < takeback; i++) {
3797             if (appData.debugMode) fprintf(debugFP, "take back move\n");
3798             SendToProgram("undo\n", &first);
3799           }
3800         }
3801 #endif
3802
3803         forwardMostMove = moveNum;
3804         if (!pausing || currentMove > forwardMostMove)
3805           currentMove = forwardMostMove;
3806     } else {
3807         /* New part of history that is not contiguous with old part */ 
3808         if (pausing && gameMode == IcsExamining) {
3809             pauseExamInvalid = TRUE;
3810             forwardMostMove = pauseExamForwardMostMove;
3811             return;
3812         }
3813         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 #if ZIPPY
3815             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3816                 // [HGM] when we will receive the move list we now request, it will be
3817                 // fed to the engine from the first move on. So if the engine is not
3818                 // in the initial position now, bring it there.
3819                 InitChessProgram(&first, 0);
3820             }
3821 #endif
3822             ics_getting_history = H_REQUESTED;
3823             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3824             SendToICS(str);
3825         }
3826         forwardMostMove = backwardMostMove = currentMove = moveNum;
3827     }
3828     
3829     /* Update the clocks */
3830     if (strchr(elapsed_time, '.')) {
3831       /* Time is in ms */
3832       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3833       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834     } else {
3835       /* Time is in seconds */
3836       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3837       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3838     }
3839       
3840
3841 #if ZIPPY
3842     if (appData.zippyPlay && newGame &&
3843         gameMode != IcsObserving && gameMode != IcsIdle &&
3844         gameMode != IcsExamining)
3845       ZippyFirstBoard(moveNum, basetime, increment);
3846 #endif
3847     
3848     /* Put the move on the move list, first converting
3849        to canonical algebraic form. */
3850     if (moveNum > 0) {
3851   if (appData.debugMode) {
3852     if (appData.debugMode) { int f = forwardMostMove;
3853         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3854                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3855                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856     }
3857     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3858     fprintf(debugFP, "moveNum = %d\n", moveNum);
3859     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3860     setbuf(debugFP, NULL);
3861   }
3862         if (moveNum <= backwardMostMove) {
3863             /* We don't know what the board looked like before
3864                this move.  Punt. */
3865             strcpy(parseList[moveNum - 1], move_str);
3866             strcat(parseList[moveNum - 1], " ");
3867             strcat(parseList[moveNum - 1], elapsed_time);
3868             moveList[moveNum - 1][0] = NULLCHAR;
3869         } else if (strcmp(move_str, "none") == 0) {
3870             // [HGM] long SAN: swapped order; test for 'none' before parsing move
3871             /* Again, we don't know what the board looked like;
3872                this is really the start of the game. */
3873             parseList[moveNum - 1][0] = NULLCHAR;
3874             moveList[moveNum - 1][0] = NULLCHAR;
3875             backwardMostMove = moveNum;
3876             startedFromSetupPosition = TRUE;
3877             fromX = fromY = toX = toY = -1;
3878         } else {
3879           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
3880           //                 So we parse the long-algebraic move string in stead of the SAN move
3881           int valid; char buf[MSG_SIZ], *prom;
3882
3883           // str looks something like "Q/a1-a2"; kill the slash
3884           if(str[1] == '/') 
3885                 sprintf(buf, "%c%s", str[0], str+2);
3886           else  strcpy(buf, str); // might be castling
3887           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
3888                 strcat(buf, prom); // long move lacks promo specification!
3889           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3890                 if(appData.debugMode) 
3891                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3892                 strcpy(move_str, buf);
3893           }
3894           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3895                                 &fromX, &fromY, &toX, &toY, &promoChar)
3896                || ParseOneMove(buf, moveNum - 1, &moveType,
3897                                 &fromX, &fromY, &toX, &toY, &promoChar);
3898           // end of long SAN patch
3899           if (valid) {
3900             (void) CoordsToAlgebraic(boards[moveNum - 1],
3901                                      PosFlags(moveNum - 1),
3902                                      fromY, fromX, toY, toX, promoChar,
3903                                      parseList[moveNum-1]);
3904             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3905               case MT_NONE:
3906               case MT_STALEMATE:
3907               default:
3908                 break;
3909               case MT_CHECK:
3910                 if(gameInfo.variant != VariantShogi)
3911                     strcat(parseList[moveNum - 1], "+");
3912                 break;
3913               case MT_CHECKMATE:
3914               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3915                 strcat(parseList[moveNum - 1], "#");
3916                 break;
3917             }
3918             strcat(parseList[moveNum - 1], " ");
3919             strcat(parseList[moveNum - 1], elapsed_time);
3920             /* currentMoveString is set as a side-effect of ParseOneMove */
3921             strcpy(moveList[moveNum - 1], currentMoveString);
3922             strcat(moveList[moveNum - 1], "\n");
3923           } else {
3924             /* Move from ICS was illegal!?  Punt. */
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3927     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928   }
3929             strcpy(parseList[moveNum - 1], move_str);
3930             strcat(parseList[moveNum - 1], " ");
3931             strcat(parseList[moveNum - 1], elapsed_time);
3932             moveList[moveNum - 1][0] = NULLCHAR;
3933             fromX = fromY = toX = toY = -1;
3934           }
3935         }
3936   if (appData.debugMode) {
3937     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3938     setbuf(debugFP, NULL);
3939   }
3940
3941 #if ZIPPY
3942         /* Send move to chess program (BEFORE animating it). */
3943         if (appData.zippyPlay && !newGame && newMove && 
3944            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945
3946             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3947                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3948                 if (moveList[moveNum - 1][0] == NULLCHAR) {
3949                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950                             move_str);
3951                     DisplayError(str, 0);
3952                 } else {
3953                     if (first.sendTime) {
3954                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955                     }
3956                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3957                     if (firstMove && !bookHit) {
3958                         firstMove = FALSE;
3959                         if (first.useColors) {
3960                           SendToProgram(gameMode == IcsPlayingWhite ?
3961                                         "white\ngo\n" :
3962                                         "black\ngo\n", &first);
3963                         } else {
3964                           SendToProgram("go\n", &first);
3965                         }
3966                         first.maybeThinking = TRUE;
3967                     }
3968                 }
3969             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3970               if (moveList[moveNum - 1][0] == NULLCHAR) {
3971                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3972                 DisplayError(str, 0);
3973               } else {
3974                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3975                 SendMoveToProgram(moveNum - 1, &first);
3976               }
3977             }
3978         }
3979 #endif
3980     }
3981
3982     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3983         /* If move comes from a remote source, animate it.  If it
3984            isn't remote, it will have already been animated. */
3985         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3986             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987         }
3988         if (!pausing && appData.highlightLastMove) {
3989             SetHighlights(fromX, fromY, toX, toY);
3990         }
3991     }
3992     
3993     /* Start the clocks */
3994     whiteFlag = blackFlag = FALSE;
3995     appData.clockMode = !(basetime == 0 && increment == 0);
3996     if (ticking == 0) {
3997       ics_clock_paused = TRUE;
3998       StopClocks();
3999     } else if (ticking == 1) {
4000       ics_clock_paused = FALSE;
4001     }
4002     if (gameMode == IcsIdle ||
4003         relation == RELATION_OBSERVING_STATIC ||
4004         relation == RELATION_EXAMINING ||
4005         ics_clock_paused)
4006       DisplayBothClocks();
4007     else
4008       StartClocks();
4009     
4010     /* Display opponents and material strengths */
4011     if (gameInfo.variant != VariantBughouse &&
4012         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4013         if (tinyLayout || smallLayout) {
4014             if(gameInfo.variant == VariantNormal)
4015                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4016                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4017                     basetime, increment);
4018             else
4019                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4020                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4021                     basetime, increment, (int) gameInfo.variant);
4022         } else {
4023             if(gameInfo.variant == VariantNormal)
4024                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4025                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4026                     basetime, increment);
4027             else
4028                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4029                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4030                     basetime, increment, VariantName(gameInfo.variant));
4031         }
4032         DisplayTitle(str);
4033   if (appData.debugMode) {
4034     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4035   }
4036     }
4037
4038    
4039     /* Display the board */
4040     if (!pausing && !appData.noGUI) {
4041       
4042       if (appData.premove)
4043           if (!gotPremove || 
4044              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4045              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4046               ClearPremoveHighlights();
4047
4048       DrawPosition(FALSE, boards[currentMove]);
4049       DisplayMove(moveNum - 1);
4050       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4051             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4052               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4053         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4054       }
4055     }
4056
4057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 #if ZIPPY
4059     if(bookHit) { // [HGM] book: simulate book reply
4060         static char bookMove[MSG_SIZ]; // a bit generous?
4061
4062         programStats.nodes = programStats.depth = programStats.time = 
4063         programStats.score = programStats.got_only_move = 0;
4064         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065
4066         strcpy(bookMove, "move ");
4067         strcat(bookMove, bookHit);
4068         HandleMachineMove(bookMove, &first);
4069     }
4070 #endif
4071 }
4072
4073 void
4074 GetMoveListEvent()
4075 {
4076     char buf[MSG_SIZ];
4077     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4078         ics_getting_history = H_REQUESTED;
4079         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4080         SendToICS(buf);
4081     }
4082 }
4083
4084 void
4085 AnalysisPeriodicEvent(force)
4086      int force;
4087 {
4088     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4089          && !force) || !appData.periodicUpdates)
4090       return;
4091
4092     /* Send . command to Crafty to collect stats */
4093     SendToProgram(".\n", &first);
4094
4095     /* Don't send another until we get a response (this makes
4096        us stop sending to old Crafty's which don't understand
4097        the "." command (sending illegal cmds resets node count & time,
4098        which looks bad)) */
4099     programStats.ok_to_send = 0;
4100 }
4101
4102 void ics_update_width(new_width)
4103         int new_width;
4104 {
4105         ics_printf("set width %d\n", new_width);
4106 }
4107
4108 void
4109 SendMoveToProgram(moveNum, cps)
4110      int moveNum;
4111      ChessProgramState *cps;
4112 {
4113     char buf[MSG_SIZ];
4114
4115     if (cps->useUsermove) {
4116       SendToProgram("usermove ", cps);
4117     }
4118     if (cps->useSAN) {
4119       char *space;
4120       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4121         int len = space - parseList[moveNum];
4122         memcpy(buf, parseList[moveNum], len);
4123         buf[len++] = '\n';
4124         buf[len] = NULLCHAR;
4125       } else {
4126         sprintf(buf, "%s\n", parseList[moveNum]);
4127       }
4128       SendToProgram(buf, cps);
4129     } else {
4130       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4131         AlphaRank(moveList[moveNum], 4);
4132         SendToProgram(moveList[moveNum], cps);
4133         AlphaRank(moveList[moveNum], 4); // and back
4134       } else
4135       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4136        * the engine. It would be nice to have a better way to identify castle 
4137        * moves here. */
4138       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4139                                                                          && cps->useOOCastle) {
4140         int fromX = moveList[moveNum][0] - AAA; 
4141         int fromY = moveList[moveNum][1] - ONE;
4142         int toX = moveList[moveNum][2] - AAA; 
4143         int toY = moveList[moveNum][3] - ONE;
4144         if((boards[moveNum][fromY][fromX] == WhiteKing 
4145             && boards[moveNum][toY][toX] == WhiteRook)
4146            || (boards[moveNum][fromY][fromX] == BlackKing 
4147                && boards[moveNum][toY][toX] == BlackRook)) {
4148           if(toX > fromX) SendToProgram("O-O\n", cps);
4149           else SendToProgram("O-O-O\n", cps);
4150         }
4151         else SendToProgram(moveList[moveNum], cps);
4152       }
4153       else SendToProgram(moveList[moveNum], cps);
4154       /* End of additions by Tord */
4155     }
4156
4157     /* [HGM] setting up the opening has brought engine in force mode! */
4158     /*       Send 'go' if we are in a mode where machine should play. */
4159     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4160         (gameMode == TwoMachinesPlay   ||
4161 #ifdef ZIPPY
4162          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4163 #endif
4164          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4165         SendToProgram("go\n", cps);
4166   if (appData.debugMode) {
4167     fprintf(debugFP, "(extra)\n");
4168   }
4169     }
4170     setboardSpoiledMachineBlack = 0;
4171 }
4172
4173 void
4174 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175      ChessMove moveType;
4176      int fromX, fromY, toX, toY;
4177 {
4178     char user_move[MSG_SIZ];
4179
4180     switch (moveType) {
4181       default:
4182         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4183                 (int)moveType, fromX, fromY, toX, toY);
4184         DisplayError(user_move + strlen("say "), 0);
4185         break;
4186       case WhiteKingSideCastle:
4187       case BlackKingSideCastle:
4188       case WhiteQueenSideCastleWild:
4189       case BlackQueenSideCastleWild:
4190       /* PUSH Fabien */
4191       case WhiteHSideCastleFR:
4192       case BlackHSideCastleFR:
4193       /* POP Fabien */
4194         sprintf(user_move, "o-o\n");
4195         break;
4196       case WhiteQueenSideCastle:
4197       case BlackQueenSideCastle:
4198       case WhiteKingSideCastleWild:
4199       case BlackKingSideCastleWild:
4200       /* PUSH Fabien */
4201       case WhiteASideCastleFR:
4202       case BlackASideCastleFR:
4203       /* POP Fabien */
4204         sprintf(user_move, "o-o-o\n");
4205         break;
4206       case WhitePromotionQueen:
4207       case BlackPromotionQueen:
4208       case WhitePromotionRook:
4209       case BlackPromotionRook:
4210       case WhitePromotionBishop:
4211       case BlackPromotionBishop:
4212       case WhitePromotionKnight:
4213       case BlackPromotionKnight:
4214       case WhitePromotionKing:
4215       case BlackPromotionKing:
4216       case WhitePromotionChancellor:
4217       case BlackPromotionChancellor:
4218       case WhitePromotionArchbishop:
4219       case BlackPromotionArchbishop:
4220         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4221             sprintf(user_move, "%c%c%c%c=%c\n",
4222                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4223                 PieceToChar(WhiteFerz));
4224         else if(gameInfo.variant == VariantGreat)
4225             sprintf(user_move, "%c%c%c%c=%c\n",
4226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4227                 PieceToChar(WhiteMan));
4228         else
4229             sprintf(user_move, "%c%c%c%c=%c\n",
4230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4231                 PieceToChar(PromoPiece(moveType)));
4232         break;
4233       case WhiteDrop:
4234       case BlackDrop:
4235         sprintf(user_move, "%c@%c%c\n",
4236                 ToUpper(PieceToChar((ChessSquare) fromX)),
4237                 AAA + toX, ONE + toY);
4238         break;
4239       case NormalMove:
4240       case WhiteCapturesEnPassant:
4241       case BlackCapturesEnPassant:
4242       case IllegalMove:  /* could be a variant we don't quite understand */
4243         sprintf(user_move, "%c%c%c%c\n",
4244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4245         break;
4246     }
4247     SendToICS(user_move);
4248     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4249         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4250 }
4251
4252 void
4253 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4254      int rf, ff, rt, ft;
4255      char promoChar;
4256      char move[7];
4257 {
4258     if (rf == DROP_RANK) {
4259         sprintf(move, "%c@%c%c\n",
4260                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261     } else {
4262         if (promoChar == 'x' || promoChar == NULLCHAR) {
4263             sprintf(move, "%c%c%c%c\n",
4264                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265         } else {
4266             sprintf(move, "%c%c%c%c%c\n",
4267                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4268         }
4269     }
4270 }
4271
4272 void
4273 ProcessICSInitScript(f)
4274      FILE *f;
4275 {
4276     char buf[MSG_SIZ];
4277
4278     while (fgets(buf, MSG_SIZ, f)) {
4279         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4280     }
4281
4282     fclose(f);
4283 }
4284
4285
4286 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 void
4288 AlphaRank(char *move, int n)
4289 {
4290 //    char *p = move, c; int x, y;
4291
4292     if (appData.debugMode) {
4293         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4294     }
4295
4296     if(move[1]=='*' && 
4297        move[2]>='0' && move[2]<='9' &&
4298        move[3]>='a' && move[3]<='x'    ) {
4299         move[1] = '@';
4300         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4301         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302     } else
4303     if(move[0]>='0' && move[0]<='9' &&
4304        move[1]>='a' && move[1]<='x' &&
4305        move[2]>='0' && move[2]<='9' &&
4306        move[3]>='a' && move[3]<='x'    ) {
4307         /* input move, Shogi -> normal */
4308         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4309         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4310         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4311         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4312     } else
4313     if(move[1]=='@' &&
4314        move[3]>='0' && move[3]<='9' &&
4315        move[2]>='a' && move[2]<='x'    ) {
4316         move[1] = '*';
4317         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4318         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4319     } else
4320     if(
4321        move[0]>='a' && move[0]<='x' &&
4322        move[3]>='0' && move[3]<='9' &&
4323        move[2]>='a' && move[2]<='x'    ) {
4324          /* output move, normal -> Shogi */
4325         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4326         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4327         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4328         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4329         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330     }
4331     if (appData.debugMode) {
4332         fprintf(debugFP, "   out = '%s'\n", move);
4333     }
4334 }
4335
4336 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 Boolean
4338 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4339      char *move;
4340      int moveNum;
4341      ChessMove *moveType;
4342      int *fromX, *fromY, *toX, *toY;
4343      char *promoChar;
4344 {       
4345     if (appData.debugMode) {
4346         fprintf(debugFP, "move to parse: %s\n", move);
4347     }
4348     *moveType = yylexstr(moveNum, move);
4349
4350     switch (*moveType) {
4351       case WhitePromotionChancellor:
4352       case BlackPromotionChancellor:
4353       case WhitePromotionArchbishop:
4354       case BlackPromotionArchbishop:
4355       case WhitePromotionQueen:
4356       case BlackPromotionQueen:
4357       case WhitePromotionRook:
4358       case BlackPromotionRook:
4359       case WhitePromotionBishop:
4360       case BlackPromotionBishop:
4361       case WhitePromotionKnight:
4362       case BlackPromotionKnight:
4363       case WhitePromotionKing:
4364       case BlackPromotionKing:
4365       case NormalMove:
4366       case WhiteCapturesEnPassant:
4367       case BlackCapturesEnPassant:
4368       case WhiteKingSideCastle:
4369       case WhiteQueenSideCastle:
4370       case BlackKingSideCastle:
4371       case BlackQueenSideCastle:
4372       case WhiteKingSideCastleWild:
4373       case WhiteQueenSideCastleWild:
4374       case BlackKingSideCastleWild:
4375       case BlackQueenSideCastleWild:
4376       /* Code added by Tord: */
4377       case WhiteHSideCastleFR:
4378       case WhiteASideCastleFR:
4379       case BlackHSideCastleFR:
4380       case BlackASideCastleFR:
4381       /* End of code added by Tord */
4382       case IllegalMove:         /* bug or odd chess variant */
4383         *fromX = currentMoveString[0] - AAA;
4384         *fromY = currentMoveString[1] - ONE;
4385         *toX = currentMoveString[2] - AAA;
4386         *toY = currentMoveString[3] - ONE;
4387         *promoChar = currentMoveString[4];
4388         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4389             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4390     if (appData.debugMode) {
4391         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392     }
4393             *fromX = *fromY = *toX = *toY = 0;
4394             return FALSE;
4395         }
4396         if (appData.testLegality) {
4397           return (*moveType != IllegalMove);
4398         } else {
4399           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4400                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4401         }
4402
4403       case WhiteDrop:
4404       case BlackDrop:
4405         *fromX = *moveType == WhiteDrop ?
4406           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4407           (int) CharToPiece(ToLower(currentMoveString[0]));
4408         *fromY = DROP_RANK;
4409         *toX = currentMoveString[2] - AAA;
4410         *toY = currentMoveString[3] - ONE;
4411         *promoChar = NULLCHAR;
4412         return TRUE;
4413
4414       case AmbiguousMove:
4415       case ImpossibleMove:
4416       case (ChessMove) 0:       /* end of file */
4417       case ElapsedTime:
4418       case Comment:
4419       case PGNTag:
4420       case NAG:
4421       case WhiteWins:
4422       case BlackWins:
4423       case GameIsDrawn:
4424       default:
4425     if (appData.debugMode) {
4426         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4427     }
4428         /* bug? */
4429         *fromX = *fromY = *toX = *toY = 0;
4430         *promoChar = NULLCHAR;
4431         return FALSE;
4432     }
4433 }
4434
4435
4436 void
4437 ParsePV(char *pv)
4438 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4439   int fromX, fromY, toX, toY; char promoChar;
4440   ChessMove moveType;
4441   Boolean valid;
4442   int nr = 0;
4443
4444   endPV = forwardMostMove;
4445   do {
4446     while(*pv == ' ') pv++;
4447     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4448     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4449 if(appData.debugMode){
4450 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 }
4452     if(!valid && nr == 0 &&
4453        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4454         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455     }
4456     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4457     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458     nr++;
4459     if(endPV+1 > framePtr) break; // no space, truncate
4460     if(!valid) break;
4461     endPV++;
4462     CopyBoard(boards[endPV], boards[endPV-1]);
4463     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4464     moveList[endPV-1][0] = fromX + AAA;
4465     moveList[endPV-1][1] = fromY + ONE;
4466     moveList[endPV-1][2] = toX + AAA;
4467     moveList[endPV-1][3] = toY + ONE;
4468     parseList[endPV-1][0] = NULLCHAR;
4469   } while(valid);
4470   currentMove = endPV;
4471   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4472   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4473                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4474   DrawPosition(TRUE, boards[currentMove]);
4475 }
4476
4477 static int lastX, lastY;
4478
4479 Boolean
4480 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4481 {
4482         int startPV;
4483
4484         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4485         lastX = x; lastY = y;
4486         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487         startPV = index;
4488       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489       index = startPV;
4490         while(buf[index] && buf[index] != '\n') index++;
4491         buf[index] = 0;
4492         ParsePV(buf+startPV);
4493         *start = startPV; *end = index-1;
4494         return TRUE;
4495 }
4496
4497 Boolean
4498 LoadPV(int x, int y)
4499 { // called on right mouse click to load PV
4500   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4501   lastX = x; lastY = y;
4502   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4503   return TRUE;
4504 }
4505
4506 void
4507 UnLoadPV()
4508 {
4509   if(endPV < 0) return;
4510   endPV = -1;
4511   currentMove = forwardMostMove;
4512   ClearPremoveHighlights();
4513   DrawPosition(TRUE, boards[currentMove]);
4514 }
4515
4516 void
4517 MovePV(int x, int y, int h)
4518 { // step through PV based on mouse coordinates (called on mouse move)
4519   int margin = h>>3, step = 0;
4520
4521   if(endPV < 0) return;
4522   // we must somehow check if right button is still down (might be released off board!)
4523   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4524   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4525   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526   if(!step) return;
4527   lastX = x; lastY = y;
4528   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4529   currentMove += step;
4530   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4531   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4532                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4533   DrawPosition(FALSE, boards[currentMove]);
4534 }
4535
4536
4537 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4538 // All positions will have equal probability, but the current method will not provide a unique
4539 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4540 #define DARK 1
4541 #define LITE 2
4542 #define ANY 3
4543
4544 int squaresLeft[4];
4545 int piecesLeft[(int)BlackPawn];
4546 int seed, nrOfShuffles;
4547
4548 void GetPositionNumber()
4549 {       // sets global variable seed
4550         int i;
4551
4552         seed = appData.defaultFrcPosition;
4553         if(seed < 0) { // randomize based on time for negative FRC position numbers
4554                 for(i=0; i<50; i++) seed += random();
4555                 seed = random() ^ random() >> 8 ^ random() << 8;
4556                 if(seed<0) seed = -seed;
4557         }
4558 }
4559
4560 int put(Board board, int pieceType, int rank, int n, int shade)
4561 // put the piece on the (n-1)-th empty squares of the given shade
4562 {
4563         int i;
4564
4565         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4566                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4567                         board[rank][i] = (ChessSquare) pieceType;
4568                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569                         squaresLeft[ANY]--;
4570                         piecesLeft[pieceType]--; 
4571                         return i;
4572                 }
4573         }
4574         return -1;
4575 }
4576
4577
4578 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4579 // calculate where the next piece goes, (any empty square), and put it there
4580 {
4581         int i;
4582
4583         i = seed % squaresLeft[shade];
4584         nrOfShuffles *= squaresLeft[shade];
4585         seed /= squaresLeft[shade];
4586         put(board, pieceType, rank, i, shade);
4587 }
4588
4589 void AddTwoPieces(Board board, int pieceType, int rank)
4590 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 {
4592         int i, n=squaresLeft[ANY], j=n-1, k;
4593
4594         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4595         i = seed % k;  // pick one
4596         nrOfShuffles *= k;
4597         seed /= k;
4598         while(i >= j) i -= j--;
4599         j = n - 1 - j; i += j;
4600         put(board, pieceType, rank, j, ANY);
4601         put(board, pieceType, rank, i, ANY);
4602 }
4603
4604 void SetUpShuffle(Board board, int number)
4605 {
4606         int i, p, first=1;
4607
4608         GetPositionNumber(); nrOfShuffles = 1;
4609
4610         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4611         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4612         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613
4614         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615
4616         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4617             p = (int) board[0][i];
4618             if(p < (int) BlackPawn) piecesLeft[p] ++;
4619             board[0][i] = EmptySquare;
4620         }
4621
4622         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4623             // shuffles restricted to allow normal castling put KRR first
4624             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4625                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4626             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4627                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4628             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4629                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4630             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4631                 put(board, WhiteRook, 0, 0, ANY);
4632             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4633         }
4634
4635         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4636             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4637             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4638                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4639                 while(piecesLeft[p] >= 2) {
4640                     AddOnePiece(board, p, 0, LITE);
4641                     AddOnePiece(board, p, 0, DARK);
4642                 }
4643                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4644             }
4645
4646         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4647             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4648             // but we leave King and Rooks for last, to possibly obey FRC restriction
4649             if(p == (int)WhiteRook) continue;
4650             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4651             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4652         }
4653
4654         // now everything is placed, except perhaps King (Unicorn) and Rooks
4655
4656         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4657             // Last King gets castling rights
4658             while(piecesLeft[(int)WhiteUnicorn]) {
4659                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4660                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4661             }
4662
4663             while(piecesLeft[(int)WhiteKing]) {
4664                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4665                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4666             }
4667
4668
4669         } else {
4670             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4671             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4672         }
4673
4674         // Only Rooks can be left; simply place them all
4675         while(piecesLeft[(int)WhiteRook]) {
4676                 i = put(board, WhiteRook, 0, 0, ANY);
4677                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4678                         if(first) {
4679                                 first=0;
4680                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4681                         }
4682                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4683                 }
4684         }
4685         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4686             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4687         }
4688
4689         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4690 }
4691
4692 int SetCharTable( char *table, const char * map )
4693 /* [HGM] moved here from winboard.c because of its general usefulness */
4694 /*       Basically a safe strcpy that uses the last character as King */
4695 {
4696     int result = FALSE; int NrPieces;
4697
4698     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4699                     && NrPieces >= 12 && !(NrPieces&1)) {
4700         int i; /* [HGM] Accept even length from 12 to 34 */
4701
4702         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4703         for( i=0; i<NrPieces/2-1; i++ ) {
4704             table[i] = map[i];
4705             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706         }
4707         table[(int) WhiteKing]  = map[NrPieces/2-1];
4708         table[(int) BlackKing]  = map[NrPieces-1];
4709
4710         result = TRUE;
4711     }
4712
4713     return result;
4714 }
4715
4716 void Prelude(Board board)
4717 {       // [HGM] superchess: random selection of exo-pieces
4718         int i, j, k; ChessSquare p; 
4719         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720
4721         GetPositionNumber(); // use FRC position number
4722
4723         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4724             SetCharTable(pieceToChar, appData.pieceToCharTable);
4725             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4726                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4727         }
4728
4729         j = seed%4;                 seed /= 4; 
4730         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4731         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4732         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4733         j = seed%3 + (seed%3 >= j); seed /= 3; 
4734         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4735         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4736         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4737         j = seed%3;                 seed /= 3; 
4738         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4739         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4740         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4741         j = seed%2 + (seed%2 >= j); seed /= 2; 
4742         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4743         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4744         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4745         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4746         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4747         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4748         put(board, exoPieces[0],    0, 0, ANY);
4749         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4750 }
4751
4752 void
4753 InitPosition(redraw)
4754      int redraw;
4755 {
4756     ChessSquare (* pieces)[BOARD_FILES];
4757     int i, j, pawnRow, overrule,
4758     oldx = gameInfo.boardWidth,
4759     oldy = gameInfo.boardHeight,
4760     oldh = gameInfo.holdingsWidth,
4761     oldv = gameInfo.variant;
4762
4763     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764
4765     /* [AS] Initialize pv info list [HGM] and game status */
4766     {
4767         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4768             pvInfoList[i].depth = 0;
4769             boards[i][EP_STATUS] = EP_NONE;
4770             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4771         }
4772
4773         initialRulePlies = 0; /* 50-move counter start */
4774
4775         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4776         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4777     }
4778
4779     
4780     /* [HGM] logic here is completely changed. In stead of full positions */
4781     /* the initialized data only consist of the two backranks. The switch */
4782     /* selects which one we will use, which is than copied to the Board   */
4783     /* initialPosition, which for the rest is initialized by Pawns and    */
4784     /* empty squares. This initial position is then copied to boards[0],  */
4785     /* possibly after shuffling, so that it remains available.            */
4786
4787     gameInfo.holdingsWidth = 0; /* default board sizes */
4788     gameInfo.boardWidth    = 8;
4789     gameInfo.boardHeight   = 8;
4790     gameInfo.holdingsSize  = 0;
4791     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4792     for(i=0; i<BOARD_FILES-2; i++)
4793       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4794     initialPosition[EP_STATUS] = EP_NONE;
4795     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
4796
4797     switch (gameInfo.variant) {
4798     case VariantFischeRandom:
4799       shuffleOpenings = TRUE;
4800     default:
4801       pieces = FIDEArray;
4802       break;
4803     case VariantShatranj:
4804       pieces = ShatranjArray;
4805       nrCastlingRights = 0;
4806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
4807       break;
4808     case VariantMakruk:
4809       pieces = makrukArray;
4810       nrCastlingRights = 0;
4811       startedFromSetupPosition = TRUE;
4812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
4813       break;
4814     case VariantTwoKings:
4815       pieces = twoKingsArray;
4816       break;
4817     case VariantCapaRandom:
4818       shuffleOpenings = TRUE;
4819     case VariantCapablanca:
4820       pieces = CapablancaArray;
4821       gameInfo.boardWidth = 10;
4822       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4823       break;
4824     case VariantGothic:
4825       pieces = GothicArray;
4826       gameInfo.boardWidth = 10;
4827       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
4828       break;
4829     case VariantJanus:
4830       pieces = JanusArray;
4831       gameInfo.boardWidth = 10;
4832       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
4833       nrCastlingRights = 6;
4834         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4835         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4836         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4837         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4838         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4839         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4840       break;
4841     case VariantFalcon:
4842       pieces = FalconArray;
4843       gameInfo.boardWidth = 10;
4844       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
4845       break;
4846     case VariantXiangqi:
4847       pieces = XiangqiArray;
4848       gameInfo.boardWidth  = 9;
4849       gameInfo.boardHeight = 10;
4850       nrCastlingRights = 0;
4851       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
4852       break;
4853     case VariantShogi:
4854       pieces = ShogiArray;
4855       gameInfo.boardWidth  = 9;
4856       gameInfo.boardHeight = 9;
4857       gameInfo.holdingsSize = 7;
4858       nrCastlingRights = 0;
4859       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
4860       break;
4861     case VariantCourier:
4862       pieces = CourierArray;
4863       gameInfo.boardWidth  = 12;
4864       nrCastlingRights = 0;
4865       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
4866       break;
4867     case VariantKnightmate:
4868       pieces = KnightmateArray;
4869       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
4870       break;
4871     case VariantFairy:
4872       pieces = fairyArray;
4873       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
4874       break;
4875     case VariantGreat:
4876       pieces = GreatArray;
4877       gameInfo.boardWidth = 10;
4878       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4879       gameInfo.holdingsSize = 8;
4880       break;
4881     case VariantSuper:
4882       pieces = FIDEArray;
4883       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4884       gameInfo.holdingsSize = 8;
4885       startedFromSetupPosition = TRUE;
4886       break;
4887     case VariantCrazyhouse:
4888     case VariantBughouse:
4889       pieces = FIDEArray;
4890       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
4891       gameInfo.holdingsSize = 5;
4892       break;
4893     case VariantWildCastle:
4894       pieces = FIDEArray;
4895       /* !!?shuffle with kings guaranteed to be on d or e file */
4896       shuffleOpenings = 1;
4897       break;
4898     case VariantNoCastle:
4899       pieces = FIDEArray;
4900       nrCastlingRights = 0;
4901       /* !!?unconstrained back-rank shuffle */
4902       shuffleOpenings = 1;
4903       break;
4904     }
4905
4906     overrule = 0;
4907     if(appData.NrFiles >= 0) {
4908         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4909         gameInfo.boardWidth = appData.NrFiles;
4910     }
4911     if(appData.NrRanks >= 0) {
4912         gameInfo.boardHeight = appData.NrRanks;
4913     }
4914     if(appData.holdingsSize >= 0) {
4915         i = appData.holdingsSize;
4916         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4917         gameInfo.holdingsSize = i;
4918     }
4919     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4920     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4921         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922
4923     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4924     if(pawnRow < 1) pawnRow = 1;
4925     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926
4927     /* User pieceToChar list overrules defaults */
4928     if(appData.pieceToCharTable != NULL)
4929         SetCharTable(pieceToChar, appData.pieceToCharTable);
4930
4931     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932
4933         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4934             s = (ChessSquare) 0; /* account holding counts in guard band */
4935         for( i=0; i<BOARD_HEIGHT; i++ )
4936             initialPosition[i][j] = s;
4937
4938         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4939         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4940         initialPosition[pawnRow][j] = WhitePawn;
4941         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4942         if(gameInfo.variant == VariantXiangqi) {
4943             if(j&1) {
4944                 initialPosition[pawnRow][j] = 
4945                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4946                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4947                    initialPosition[2][j] = WhiteCannon;
4948                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4949                 }
4950             }
4951         }
4952         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
4953     }
4954     if( (gameInfo.variant == VariantShogi) && !overrule ) {
4955
4956             j=BOARD_LEFT+1;
4957             initialPosition[1][j] = WhiteBishop;
4958             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959             j=BOARD_RGHT-2;
4960             initialPosition[1][j] = WhiteRook;
4961             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4962     }
4963
4964     if( nrCastlingRights == -1) {
4965         /* [HGM] Build normal castling rights (must be done after board sizing!) */
4966         /*       This sets default castling rights from none to normal corners   */
4967         /* Variants with other castling rights must set them themselves above    */
4968         nrCastlingRights = 6;
4969        
4970         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4971         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4972         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4973         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4974         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4975         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4976      }
4977
4978      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4979      if(gameInfo.variant == VariantGreat) { // promotion commoners
4980         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4981         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4982         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4983         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984      }
4985   if (appData.debugMode) {
4986     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987   }
4988     if(shuffleOpenings) {
4989         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4990         startedFromSetupPosition = TRUE;
4991     }
4992     if(startedFromPositionFile) {
4993       /* [HGM] loadPos: use PositionFile for every new game */
4994       CopyBoard(initialPosition, filePosition);
4995       for(i=0; i<nrCastlingRights; i++)
4996           initialRights[i] = filePosition[CASTLING][i];
4997       startedFromSetupPosition = TRUE;
4998     }
4999
5000     CopyBoard(boards[0], initialPosition);
5001
5002     if(oldx != gameInfo.boardWidth ||
5003        oldy != gameInfo.boardHeight ||
5004        oldh != gameInfo.holdingsWidth
5005 #ifdef GOTHIC
5006        || oldv == VariantGothic ||        // For licensing popups
5007        gameInfo.variant == VariantGothic
5008 #endif
5009 #ifdef FALCON
5010        || oldv == VariantFalcon ||
5011        gameInfo.variant == VariantFalcon
5012 #endif
5013                                          )
5014             InitDrawingSizes(-2 ,0);
5015
5016     if (redraw)
5017       DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 void
5021 SendBoard(cps, moveNum)
5022      ChessProgramState *cps;
5023      int moveNum;
5024 {
5025     char message[MSG_SIZ];
5026     
5027     if (cps->useSetboard) {
5028       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5029       sprintf(message, "setboard %s\n", fen);
5030       SendToProgram(message, cps);
5031       free(fen);
5032
5033     } else {
5034       ChessSquare *bp;
5035       int i, j;
5036       /* Kludge to set black to move, avoiding the troublesome and now
5037        * deprecated "black" command.
5038        */
5039       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040
5041       SendToProgram("edit\n", cps);
5042       SendToProgram("#\n", cps);
5043       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5044         bp = &boards[moveNum][i][BOARD_LEFT];
5045         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5046           if ((int) *bp < (int) BlackPawn) {
5047             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5048                     AAA + j, ONE + i);
5049             if(message[0] == '+' || message[0] == '~') {
5050                 sprintf(message, "%c%c%c+\n",
5051                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5052                         AAA + j, ONE + i);
5053             }
5054             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5055                 message[1] = BOARD_RGHT   - 1 - j + '1';
5056                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057             }
5058             SendToProgram(message, cps);
5059           }
5060         }
5061       }
5062     
5063       SendToProgram("c\n", cps);
5064       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5065         bp = &boards[moveNum][i][BOARD_LEFT];
5066         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5067           if (((int) *bp != (int) EmptySquare)
5068               && ((int) *bp >= (int) BlackPawn)) {
5069             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070                     AAA + j, ONE + i);
5071             if(message[0] == '+' || message[0] == '~') {
5072                 sprintf(message, "%c%c%c+\n",
5073                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5074                         AAA + j, ONE + i);
5075             }
5076             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5077                 message[1] = BOARD_RGHT   - 1 - j + '1';
5078                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079             }
5080             SendToProgram(message, cps);
5081           }
5082         }
5083       }
5084     
5085       SendToProgram(".\n", cps);
5086     }
5087     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5088 }
5089
5090 int
5091 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 {
5093     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5094     /* [HGM] add Shogi promotions */
5095     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5096     ChessSquare piece;
5097     ChessMove moveType;
5098     Boolean premove;
5099
5100     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5101     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5102
5103     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5104       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5105         return FALSE;
5106
5107     piece = boards[currentMove][fromY][fromX];
5108     if(gameInfo.variant == VariantShogi) {
5109         promotionZoneSize = 3;
5110         highestPromotingPiece = (int)WhiteFerz;
5111     } else if(gameInfo.variant == VariantMakruk) {
5112         promotionZoneSize = 3;
5113     }
5114
5115     // next weed out all moves that do not touch the promotion zone at all
5116     if((int)piece >= BlackPawn) {
5117         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118              return FALSE;
5119         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120     } else {
5121         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5122            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5123     }
5124
5125     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126
5127     // weed out mandatory Shogi promotions
5128     if(gameInfo.variant == VariantShogi) {
5129         if(piece >= BlackPawn) {
5130             if(toY == 0 && piece == BlackPawn ||
5131                toY == 0 && piece == BlackQueen ||
5132                toY <= 1 && piece == BlackKnight) {
5133                 *promoChoice = '+';
5134                 return FALSE;
5135             }
5136         } else {
5137             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5138                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5139                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5140                 *promoChoice = '+';
5141                 return FALSE;
5142             }
5143         }
5144     }
5145
5146     // weed out obviously illegal Pawn moves
5147     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5148         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5149         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5150         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5151         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5152         // note we are not allowed to test for valid (non-)capture, due to premove
5153     }
5154
5155     // we either have a choice what to promote to, or (in Shogi) whether to promote
5156     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5157         *promoChoice = PieceToChar(BlackFerz);  // no choice
5158         return FALSE;
5159     }
5160     if(appData.alwaysPromoteToQueen) { // predetermined
5161         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5162              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5163         else *promoChoice = PieceToChar(BlackQueen);
5164         return FALSE;
5165     }
5166
5167     // suppress promotion popup on illegal moves that are not premoves
5168     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5169               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5170     if(appData.testLegality && !premove) {
5171         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5172                         fromY, fromX, toY, toX, NULLCHAR);
5173         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5174            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5175             return FALSE;
5176     }
5177
5178     return TRUE;
5179 }
5180
5181 int
5182 InPalace(row, column)
5183      int row, column;
5184 {   /* [HGM] for Xiangqi */
5185     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5186          column < (BOARD_WIDTH + 4)/2 &&
5187          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5188     return FALSE;
5189 }
5190
5191 int
5192 PieceForSquare (x, y)
5193      int x;
5194      int y;
5195 {
5196   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5197      return -1;
5198   else
5199      return boards[currentMove][y][x];
5200 }
5201
5202 int
5203 OKToStartUserMove(x, y)
5204      int x, y;
5205 {
5206     ChessSquare from_piece;
5207     int white_piece;
5208
5209     if (matchMode) return FALSE;
5210     if (gameMode == EditPosition) return TRUE;
5211
5212     if (x >= 0 && y >= 0)
5213       from_piece = boards[currentMove][y][x];
5214     else
5215       from_piece = EmptySquare;
5216
5217     if (from_piece == EmptySquare) return FALSE;
5218
5219     white_piece = (int)from_piece >= (int)WhitePawn &&
5220       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5221
5222     switch (gameMode) {
5223       case PlayFromGameFile:
5224       case AnalyzeFile:
5225       case TwoMachinesPlay:
5226       case EndOfGame:
5227         return FALSE;
5228
5229       case IcsObserving:
5230       case IcsIdle:
5231         return FALSE;
5232
5233       case MachinePlaysWhite:
5234       case IcsPlayingBlack:
5235         if (appData.zippyPlay) return FALSE;
5236         if (white_piece) {
5237             DisplayMoveError(_("You are playing Black"));
5238             return FALSE;
5239         }
5240         break;
5241
5242       case MachinePlaysBlack:
5243       case IcsPlayingWhite:
5244         if (appData.zippyPlay) return FALSE;
5245         if (!white_piece) {
5246             DisplayMoveError(_("You are playing White"));
5247             return FALSE;
5248         }
5249         break;
5250
5251       case EditGame:
5252         if (!white_piece && WhiteOnMove(currentMove)) {
5253             DisplayMoveError(_("It is White's turn"));
5254             return FALSE;
5255         }           
5256         if (white_piece && !WhiteOnMove(currentMove)) {
5257             DisplayMoveError(_("It is Black's turn"));
5258             return FALSE;
5259         }           
5260         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5261             /* Editing correspondence game history */
5262             /* Could disallow this or prompt for confirmation */
5263             cmailOldMove = -1;
5264         }
5265         break;
5266
5267       case BeginningOfGame:
5268         if (appData.icsActive) return FALSE;
5269         if (!appData.noChessProgram) {
5270             if (!white_piece) {
5271                 DisplayMoveError(_("You are playing White"));
5272                 return FALSE;
5273             }
5274         }
5275         break;
5276         
5277       case Training:
5278         if (!white_piece && WhiteOnMove(currentMove)) {
5279             DisplayMoveError(_("It is White's turn"));
5280             return FALSE;
5281         }           
5282         if (white_piece && !WhiteOnMove(currentMove)) {
5283             DisplayMoveError(_("It is Black's turn"));
5284             return FALSE;
5285         }           
5286         break;
5287
5288       default:
5289       case IcsExamining:
5290         break;
5291     }
5292     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5293         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5294         && gameMode != AnalyzeFile && gameMode != Training) {
5295         DisplayMoveError(_("Displayed position is not current"));
5296         return FALSE;
5297     }
5298     return TRUE;
5299 }
5300
5301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5303 int lastLoadGameUseList = FALSE;
5304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5305 ChessMove lastLoadGameStart = (ChessMove) 0;
5306
5307 ChessMove
5308 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5309      int fromX, fromY, toX, toY;
5310      int promoChar;
5311      Boolean captureOwn;
5312 {
5313     ChessMove moveType;
5314     ChessSquare pdown, pup;
5315
5316     /* Check if the user is playing in turn.  This is complicated because we
5317        let the user "pick up" a piece before it is his turn.  So the piece he
5318        tried to pick up may have been captured by the time he puts it down!
5319        Therefore we use the color the user is supposed to be playing in this
5320        test, not the color of the piece that is currently on the starting
5321        square---except in EditGame mode, where the user is playing both
5322        sides; fortunately there the capture race can't happen.  (It can
5323        now happen in IcsExamining mode, but that's just too bad.  The user
5324        will get a somewhat confusing message in that case.)
5325        */
5326
5327     switch (gameMode) {
5328       case PlayFromGameFile:
5329       case AnalyzeFile:
5330       case TwoMachinesPlay:
5331       case EndOfGame:
5332       case IcsObserving:
5333       case IcsIdle:
5334         /* We switched into a game mode where moves are not accepted,
5335            perhaps while the mouse button was down. */
5336         return ImpossibleMove;
5337
5338       case MachinePlaysWhite:
5339         /* User is moving for Black */
5340         if (WhiteOnMove(currentMove)) {
5341             DisplayMoveError(_("It is White's turn"));
5342             return ImpossibleMove;
5343         }
5344         break;
5345
5346       case MachinePlaysBlack:
5347         /* User is moving for White */
5348         if (!WhiteOnMove(currentMove)) {
5349             DisplayMoveError(_("It is Black's turn"));
5350             return ImpossibleMove;
5351         }
5352         break;
5353
5354       case EditGame:
5355       case IcsExamining:
5356       case BeginningOfGame:
5357       case AnalyzeMode:
5358       case Training:
5359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5361             /* User is moving for Black */
5362             if (WhiteOnMove(currentMove)) {
5363                 DisplayMoveError(_("It is White's turn"));
5364                 return ImpossibleMove;
5365             }
5366         } else {
5367             /* User is moving for White */
5368             if (!WhiteOnMove(currentMove)) {
5369                 DisplayMoveError(_("It is Black's turn"));
5370                 return ImpossibleMove;
5371             }
5372         }
5373         break;
5374
5375       case IcsPlayingBlack:
5376         /* User is moving for Black */
5377         if (WhiteOnMove(currentMove)) {
5378             if (!appData.premove) {
5379                 DisplayMoveError(_("It is White's turn"));
5380             } else if (toX >= 0 && toY >= 0) {
5381                 premoveToX = toX;
5382                 premoveToY = toY;
5383                 premoveFromX = fromX;
5384                 premoveFromY = fromY;
5385                 premovePromoChar = promoChar;
5386                 gotPremove = 1;
5387                 if (appData.debugMode) 
5388                     fprintf(debugFP, "Got premove: fromX %d,"
5389                             "fromY %d, toX %d, toY %d\n",
5390                             fromX, fromY, toX, toY);
5391             }
5392             return ImpossibleMove;
5393         }
5394         break;
5395
5396       case IcsPlayingWhite:
5397         /* User is moving for White */
5398         if (!WhiteOnMove(currentMove)) {
5399             if (!appData.premove) {
5400                 DisplayMoveError(_("It is Black's turn"));
5401             } else if (toX >= 0 && toY >= 0) {
5402                 premoveToX = toX;
5403                 premoveToY = toY;
5404                 premoveFromX = fromX;
5405                 premoveFromY = fromY;
5406                 premovePromoChar = promoChar;
5407                 gotPremove = 1;
5408                 if (appData.debugMode) 
5409                     fprintf(debugFP, "Got premove: fromX %d,"
5410                             "fromY %d, toX %d, toY %d\n",
5411                             fromX, fromY, toX, toY);
5412             }
5413             return ImpossibleMove;
5414         }
5415         break;
5416
5417       default:
5418         break;
5419
5420       case EditPosition:
5421         /* EditPosition, empty square, or different color piece;
5422            click-click move is possible */
5423         if (toX == -2 || toY == -2) {
5424             boards[0][fromY][fromX] = EmptySquare;
5425             return AmbiguousMove;
5426         } else if (toX >= 0 && toY >= 0) {
5427             boards[0][toY][toX] = boards[0][fromY][fromX];
5428             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5429                 if(boards[0][fromY][0] != EmptySquare) {
5430                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5431                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5432                 }
5433             } else
5434             if(fromX == BOARD_RGHT+1) {
5435                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5436                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5437                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5438                 }
5439             } else
5440             boards[0][fromY][fromX] = EmptySquare;
5441             return AmbiguousMove;
5442         }
5443         return ImpossibleMove;
5444     }
5445
5446     if(toX < 0 || toY < 0) return ImpossibleMove;
5447     pdown = boards[currentMove][fromY][fromX];
5448     pup = boards[currentMove][toY][toX];
5449
5450     /* [HGM] If move started in holdings, it means a drop */
5451     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5452          if( pup != EmptySquare ) return ImpossibleMove;
5453          if(appData.testLegality) {
5454              /* it would be more logical if LegalityTest() also figured out
5455               * which drops are legal. For now we forbid pawns on back rank.
5456               * Shogi is on its own here...
5457               */
5458              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5459                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5460                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461          }
5462          return WhiteDrop; /* Not needed to specify white or black yet */
5463     }
5464
5465     userOfferedDraw = FALSE;
5466         
5467     /* [HGM] always test for legality, to get promotion info */
5468     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5469                                          fromY, fromX, toY, toX, promoChar);
5470     /* [HGM] but possibly ignore an IllegalMove result */
5471     if (appData.testLegality) {
5472         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5473             DisplayMoveError(_("Illegal move"));
5474             return ImpossibleMove;
5475         }
5476     }
5477
5478     return moveType;
5479     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5480        function is made into one that returns an OK move type if FinishMove
5481        should be called. This to give the calling driver routine the
5482        opportunity to finish the userMove input with a promotion popup,
5483        without bothering the user with this for invalid or illegal moves */
5484
5485 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5486 }
5487
5488 /* Common tail of UserMoveEvent and DropMenuEvent */
5489 int
5490 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5491      ChessMove moveType;
5492      int fromX, fromY, toX, toY;
5493      /*char*/int promoChar;
5494 {
5495     char *bookHit = 0;
5496
5497     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5498         // [HGM] superchess: suppress promotions to non-available piece
5499         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5500         if(WhiteOnMove(currentMove)) {
5501             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5502         } else {
5503             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5504         }
5505     }
5506
5507     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5508        move type in caller when we know the move is a legal promotion */
5509     if(moveType == NormalMove && promoChar)
5510         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5511
5512     /* [HGM] convert drag-and-drop piece drops to standard form */
5513     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5514          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5515            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5516                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5517            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5518            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5519            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5520            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5521          fromY = DROP_RANK;
5522     }
5523
5524     /* [HGM] <popupFix> The following if has been moved here from
5525        UserMoveEvent(). Because it seemed to belong here (why not allow
5526        piece drops in training games?), and because it can only be
5527        performed after it is known to what we promote. */
5528     if (gameMode == Training) {
5529       /* compare the move played on the board to the next move in the
5530        * game. If they match, display the move and the opponent's response. 
5531        * If they don't match, display an error message.
5532        */
5533       int saveAnimate;
5534       Board testBoard;
5535       CopyBoard(testBoard, boards[currentMove]);
5536       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5537
5538       if (CompareBoards(testBoard, boards[currentMove+1])) {
5539         ForwardInner(currentMove+1);
5540
5541         /* Autoplay the opponent's response.
5542          * if appData.animate was TRUE when Training mode was entered,
5543          * the response will be animated.
5544          */
5545         saveAnimate = appData.animate;
5546         appData.animate = animateTraining;
5547         ForwardInner(currentMove+1);
5548         appData.animate = saveAnimate;
5549
5550         /* check for the end of the game */
5551         if (currentMove >= forwardMostMove) {
5552           gameMode = PlayFromGameFile;
5553           ModeHighlight();
5554           SetTrainingModeOff();
5555           DisplayInformation(_("End of game"));
5556         }
5557       } else {
5558         DisplayError(_("Incorrect move"), 0);
5559       }
5560       return 1;
5561     }
5562
5563   /* Ok, now we know that the move is good, so we can kill
5564      the previous line in Analysis Mode */
5565   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5566                                 && currentMove < forwardMostMove) {
5567     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5568   }
5569
5570   /* If we need the chess program but it's dead, restart it */
5571   ResurrectChessProgram();
5572
5573   /* A user move restarts a paused game*/
5574   if (pausing)
5575     PauseEvent();
5576
5577   thinkOutput[0] = NULLCHAR;
5578
5579   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5580
5581   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5582
5583   if (gameMode == BeginningOfGame) {
5584     if (appData.noChessProgram) {
5585       gameMode = EditGame;
5586       SetGameInfo();
5587     } else {
5588       char buf[MSG_SIZ];
5589       gameMode = MachinePlaysBlack;
5590       StartClocks();
5591       SetGameInfo();
5592       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5593       DisplayTitle(buf);
5594       if (first.sendName) {
5595         sprintf(buf, "name %s\n", gameInfo.white);
5596         SendToProgram(buf, &first);
5597       }
5598       StartClocks();
5599     }
5600     ModeHighlight();
5601   }
5602
5603   /* Relay move to ICS or chess engine */
5604   if (appData.icsActive) {
5605     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5606         gameMode == IcsExamining) {
5607       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5608       ics_user_moved = 1;
5609     }
5610   } else {
5611     if (first.sendTime && (gameMode == BeginningOfGame ||
5612                            gameMode == MachinePlaysWhite ||
5613                            gameMode == MachinePlaysBlack)) {
5614       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5615     }
5616     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5617          // [HGM] book: if program might be playing, let it use book
5618         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5619         first.maybeThinking = TRUE;
5620     } else SendMoveToProgram(forwardMostMove-1, &first);
5621     if (currentMove == cmailOldMove + 1) {
5622       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5623     }
5624   }
5625
5626   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5627
5628   switch (gameMode) {
5629   case EditGame:
5630     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5631     case MT_NONE:
5632     case MT_CHECK:
5633       break;
5634     case MT_CHECKMATE:
5635     case MT_STAINMATE:
5636       if (WhiteOnMove(currentMove)) {
5637         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5638       } else {
5639         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5640       }
5641       break;
5642     case MT_STALEMATE:
5643       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5644       break;
5645     }
5646     break;
5647     
5648   case MachinePlaysBlack:
5649   case MachinePlaysWhite:
5650     /* disable certain menu options while machine is thinking */
5651     SetMachineThinkingEnables();
5652     break;
5653
5654   default:
5655     break;
5656   }
5657
5658   if(bookHit) { // [HGM] book: simulate book reply
5659         static char bookMove[MSG_SIZ]; // a bit generous?
5660
5661         programStats.nodes = programStats.depth = programStats.time = 
5662         programStats.score = programStats.got_only_move = 0;
5663         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5664
5665         strcpy(bookMove, "move ");
5666         strcat(bookMove, bookHit);
5667         HandleMachineMove(bookMove, &first);
5668   }
5669   return 1;
5670 }
5671
5672 void
5673 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5674      int fromX, fromY, toX, toY;
5675      int promoChar;
5676 {
5677     /* [HGM] This routine was added to allow calling of its two logical
5678        parts from other modules in the old way. Before, UserMoveEvent()
5679        automatically called FinishMove() if the move was OK, and returned
5680        otherwise. I separated the two, in order to make it possible to
5681        slip a promotion popup in between. But that it always needs two
5682        calls, to the first part, (now called UserMoveTest() ), and to
5683        FinishMove if the first part succeeded. Calls that do not need
5684        to do anything in between, can call this routine the old way. 
5685     */
5686     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5687 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5688     if(moveType == AmbiguousMove)
5689         DrawPosition(FALSE, boards[currentMove]);
5690     else if(moveType != ImpossibleMove && moveType != Comment)
5691         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5692 }
5693
5694 void
5695 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5696      Board board;
5697      int flags;
5698      ChessMove kind;
5699      int rf, ff, rt, ft;
5700      VOIDSTAR closure;
5701 {
5702     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5703     Markers *m = (Markers *) closure;
5704     if(rf == fromY && ff == fromX)
5705         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5706                          || kind == WhiteCapturesEnPassant
5707                          || kind == BlackCapturesEnPassant);
5708     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5709 }
5710
5711 void
5712 MarkTargetSquares(int clear)
5713 {
5714   int x, y;
5715   if(!appData.markers || !appData.highlightDragging || 
5716      !appData.testLegality || gameMode == EditPosition) return;
5717   if(clear) {
5718     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5719   } else {
5720     int capt = 0;
5721     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5722     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5723       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5724       if(capt)
5725       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5726     }
5727   }
5728   DrawPosition(TRUE, NULL);
5729 }
5730
5731 void LeftClick(ClickType clickType, int xPix, int yPix)
5732 {
5733     int x, y;
5734     Boolean saveAnimate;
5735     static int second = 0, promotionChoice = 0;
5736     char promoChoice = NULLCHAR;
5737
5738     if (clickType == Press) ErrorPopDown();
5739     MarkTargetSquares(1);
5740
5741     x = EventToSquare(xPix, BOARD_WIDTH);
5742     y = EventToSquare(yPix, BOARD_HEIGHT);
5743     if (!flipView && y >= 0) {
5744         y = BOARD_HEIGHT - 1 - y;
5745     }
5746     if (flipView && x >= 0) {
5747         x = BOARD_WIDTH - 1 - x;
5748     }
5749
5750     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5751         if(clickType == Release) return; // ignore upclick of click-click destination
5752         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5753         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5754         if(gameInfo.holdingsWidth && 
5755                 (WhiteOnMove(currentMove) 
5756                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5757                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5758             // click in right holdings, for determining promotion piece
5759             ChessSquare p = boards[currentMove][y][x];
5760             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5761             if(p != EmptySquare) {
5762                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5763                 fromX = fromY = -1;
5764                 return;
5765             }
5766         }
5767         DrawPosition(FALSE, boards[currentMove]);
5768         return;
5769     }
5770
5771     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5772     if(clickType == Press
5773             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5774               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5775               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5776         return;
5777
5778     if (fromX == -1) {
5779         if (clickType == Press) {
5780             /* First square */
5781             if (OKToStartUserMove(x, y)) {
5782                 fromX = x;
5783                 fromY = y;
5784                 second = 0;
5785                 MarkTargetSquares(0);
5786                 DragPieceBegin(xPix, yPix);
5787                 if (appData.highlightDragging) {
5788                     SetHighlights(x, y, -1, -1);
5789                 }
5790             }
5791         }
5792         return;
5793     }
5794
5795     /* fromX != -1 */
5796     if (clickType == Press && gameMode != EditPosition) {
5797         ChessSquare fromP;
5798         ChessSquare toP;
5799         int frc;
5800
5801         // ignore off-board to clicks
5802         if(y < 0 || x < 0) return;
5803
5804         /* Check if clicking again on the same color piece */
5805         fromP = boards[currentMove][fromY][fromX];
5806         toP = boards[currentMove][y][x];
5807         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5808         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5809              WhitePawn <= toP && toP <= WhiteKing &&
5810              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5811              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5812             (BlackPawn <= fromP && fromP <= BlackKing && 
5813              BlackPawn <= toP && toP <= BlackKing &&
5814              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5815              !(fromP == BlackKing && toP == BlackRook && frc))) {
5816             /* Clicked again on same color piece -- changed his mind */
5817             second = (x == fromX && y == fromY);
5818             if (appData.highlightDragging) {
5819                 SetHighlights(x, y, -1, -1);
5820             } else {
5821                 ClearHighlights();
5822             }
5823             if (OKToStartUserMove(x, y)) {
5824                 fromX = x;
5825                 fromY = y;
5826                 MarkTargetSquares(0);
5827                 DragPieceBegin(xPix, yPix);
5828             }
5829             return;
5830         }
5831         // ignore clicks on holdings
5832         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5833     }
5834
5835     if (clickType == Release && x == fromX && y == fromY) {
5836         DragPieceEnd(xPix, yPix);
5837         if (appData.animateDragging) {
5838             /* Undo animation damage if any */
5839             DrawPosition(FALSE, NULL);
5840         }
5841         if (second) {
5842             /* Second up/down in same square; just abort move */
5843             second = 0;
5844             fromX = fromY = -1;
5845             ClearHighlights();
5846             gotPremove = 0;
5847             ClearPremoveHighlights();
5848         } else {
5849             /* First upclick in same square; start click-click mode */
5850             SetHighlights(x, y, -1, -1);
5851         }
5852         return;
5853     }
5854
5855     /* we now have a different from- and (possibly off-board) to-square */
5856     /* Completed move */
5857     toX = x;
5858     toY = y;
5859     saveAnimate = appData.animate;
5860     if (clickType == Press) {
5861         /* Finish clickclick move */
5862         if (appData.animate || appData.highlightLastMove) {
5863             SetHighlights(fromX, fromY, toX, toY);
5864         } else {
5865             ClearHighlights();
5866         }
5867     } else {
5868         /* Finish drag move */
5869         if (appData.highlightLastMove) {
5870             SetHighlights(fromX, fromY, toX, toY);
5871         } else {
5872             ClearHighlights();
5873         }
5874         DragPieceEnd(xPix, yPix);
5875         /* Don't animate move and drag both */
5876         appData.animate = FALSE;
5877     }
5878
5879     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5880     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5881         ChessSquare piece = boards[currentMove][fromY][fromX];
5882         if(gameMode == EditPosition && piece != EmptySquare &&
5883            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5884             int n;
5885              
5886             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5887                 n = PieceToNumber(piece - (int)BlackPawn);
5888                 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5889                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5890                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5891             } else
5892             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5893                 n = PieceToNumber(piece);
5894                 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5895                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5896                 boards[currentMove][n][BOARD_WIDTH-2]++;
5897             }
5898             boards[currentMove][fromY][fromX] = EmptySquare;
5899         }
5900         ClearHighlights();
5901         fromX = fromY = -1;
5902         DrawPosition(TRUE, boards[currentMove]);
5903         return;
5904     }
5905
5906     // off-board moves should not be highlighted
5907     if(x < 0 || x < 0) ClearHighlights();
5908
5909     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5910         SetHighlights(fromX, fromY, toX, toY);
5911         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5912             // [HGM] super: promotion to captured piece selected from holdings
5913             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5914             promotionChoice = TRUE;
5915             // kludge follows to temporarily execute move on display, without promoting yet
5916             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5917             boards[currentMove][toY][toX] = p;
5918             DrawPosition(FALSE, boards[currentMove]);
5919             boards[currentMove][fromY][fromX] = p; // take back, but display stays
5920             boards[currentMove][toY][toX] = q;
5921             DisplayMessage("Click in holdings to choose piece", "");
5922             return;
5923         }
5924         PromotionPopUp();
5925     } else {
5926         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5927         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5928         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5929         fromX = fromY = -1;
5930     }
5931     appData.animate = saveAnimate;
5932     if (appData.animate || appData.animateDragging) {
5933         /* Undo animation damage if needed */
5934         DrawPosition(FALSE, NULL);
5935     }
5936 }
5937
5938 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5939 {
5940 //    char * hint = lastHint;
5941     FrontEndProgramStats stats;
5942
5943     stats.which = cps == &first ? 0 : 1;
5944     stats.depth = cpstats->depth;
5945     stats.nodes = cpstats->nodes;
5946     stats.score = cpstats->score;
5947     stats.time = cpstats->time;
5948     stats.pv = cpstats->movelist;
5949     stats.hint = lastHint;
5950     stats.an_move_index = 0;
5951     stats.an_move_count = 0;
5952
5953     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5954         stats.hint = cpstats->move_name;
5955         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5956         stats.an_move_count = cpstats->nr_moves;
5957     }
5958
5959     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5960
5961     SetProgramStats( &stats );
5962 }
5963
5964 int
5965 Adjudicate(ChessProgramState *cps)
5966 {       // [HGM] some adjudications useful with buggy engines
5967         // [HGM] adjudicate: made into separate routine, which now can be called after every move
5968         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5969         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
5970         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5971         int k, count = 0; static int bare = 1;
5972         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5973         Boolean canAdjudicate = !appData.icsActive;
5974
5975         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5976         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5977             if( appData.testLegality )
5978             {   /* [HGM] Some more adjudications for obstinate engines */
5979                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5980                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5981                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5982                 static int moveCount = 6;
5983                 ChessMove result;
5984                 char *reason = NULL;
5985
5986                 /* Count what is on board. */
5987                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5988                 {   ChessSquare p = boards[forwardMostMove][i][j];
5989                     int m=i;
5990
5991                     switch((int) p)
5992                     {   /* count B,N,R and other of each side */
5993                         case WhiteKing:
5994                         case BlackKing:
5995                              NrK++; break; // [HGM] atomic: count Kings
5996                         case WhiteKnight:
5997                              NrWN++; break;
5998                         case WhiteBishop:
5999                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6000                              bishopsColor |= 1 << ((i^j)&1);
6001                              NrWB++; break;
6002                         case BlackKnight:
6003                              NrBN++; break;
6004                         case BlackBishop:
6005                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6006                              bishopsColor |= 1 << ((i^j)&1);
6007                              NrBB++; break;
6008                         case WhiteRook:
6009                              NrWR++; break;
6010                         case BlackRook:
6011                              NrBR++; break;
6012                         case WhiteQueen:
6013                              NrWQ++; break;
6014                         case BlackQueen:
6015                              NrBQ++; break;
6016                         case EmptySquare: 
6017                              break;
6018                         case BlackPawn:
6019                              m = 7-i;
6020                         case WhitePawn:
6021                              PawnAdvance += m; NrPawns++;
6022                     }
6023                     NrPieces += (p != EmptySquare);
6024                     NrW += ((int)p < (int)BlackPawn);
6025                     if(gameInfo.variant == VariantXiangqi && 
6026                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6027                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6028                         NrW -= ((int)p < (int)BlackPawn);
6029                     }
6030                 }
6031
6032                 /* Some material-based adjudications that have to be made before stalemate test */
6033                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6034                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6035                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6036                      if(canAdjudicate && appData.checkMates) {
6037                          if(engineOpponent)
6038                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6039                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6040                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6041                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6042                          return 1;
6043                      }
6044                 }
6045
6046                 /* Bare King in Shatranj (loses) or Losers (wins) */
6047                 if( NrW == 1 || NrPieces - NrW == 1) {
6048                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6049                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6050                      if(canAdjudicate && appData.checkMates) {
6051                          if(engineOpponent)
6052                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6053                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6054                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6055                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6056                          return 1;
6057                      }
6058                   } else
6059                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6060                   {    /* bare King */
6061                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6062                         if(canAdjudicate && appData.checkMates) {
6063                             /* but only adjudicate if adjudication enabled */
6064                             if(engineOpponent)
6065                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6066                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6067                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6068                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6069                             return 1;
6070                         }
6071                   }
6072                 } else bare = 1;
6073
6074
6075             // don't wait for engine to announce game end if we can judge ourselves
6076             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6077               case MT_CHECK:
6078                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6079                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6080                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6081                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6082                             checkCnt++;
6083                         if(checkCnt >= 2) {
6084                             reason = "Xboard adjudication: 3rd check";
6085                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6086                             break;
6087                         }
6088                     }
6089                 }
6090               case MT_NONE:
6091               default:
6092                 break;
6093               case MT_STALEMATE:
6094               case MT_STAINMATE:
6095                 reason = "Xboard adjudication: Stalemate";
6096                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6097                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6098                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6099                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6100                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6101                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6102                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6103                                                                         EP_CHECKMATE : EP_WINS);
6104                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6105                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6106                 }
6107                 break;
6108               case MT_CHECKMATE:
6109                 reason = "Xboard adjudication: Checkmate";
6110                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6111                 break;
6112             }
6113
6114                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6115                     case EP_STALEMATE:
6116                         result = GameIsDrawn; break;
6117                     case EP_CHECKMATE:
6118                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6119                     case EP_WINS:
6120                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6121                     default:
6122                         result = (ChessMove) 0;
6123                 }
6124                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6125                     if(engineOpponent)
6126                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6127                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6128                     GameEnds( result, reason, GE_XBOARD );
6129                     return 1;
6130                 }
6131
6132                 /* Next absolutely insufficient mating material. */
6133                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6134                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6135                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6136                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6137                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6138
6139                      /* always flag draws, for judging claims */
6140                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6141
6142                      if(canAdjudicate && appData.materialDraws) {
6143                          /* but only adjudicate them if adjudication enabled */
6144                          if(engineOpponent) {
6145                            SendToProgram("force\n", engineOpponent); // suppress reply
6146                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6147                          }
6148                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6149                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6150                          return 1;
6151                      }
6152                 }
6153
6154                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6155                 if(NrPieces == 4 && 
6156                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6157                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6158                    || NrWN==2 || NrBN==2     /* KNNK */
6159                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6160                   ) ) {
6161                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6162                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6163                           if(engineOpponent) {
6164                             SendToProgram("force\n", engineOpponent); // suppress reply
6165                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6166                           }
6167                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6168                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6169                           return 1;
6170                      }
6171                 } else moveCount = 6;
6172             }
6173         }
6174           
6175         if (appData.debugMode) { int i;
6176             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6177                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6178                     appData.drawRepeats);
6179             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6180               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6181             
6182         }
6183
6184         // Repetition draws and 50-move rule can be applied independently of legality testing
6185
6186                 /* Check for rep-draws */
6187                 count = 0;
6188                 for(k = forwardMostMove-2;
6189                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6190                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6191                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6192                     k-=2)
6193                 {   int rights=0;
6194                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6195                         /* compare castling rights */
6196                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6197                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6198                                 rights++; /* King lost rights, while rook still had them */
6199                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6200                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6201                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6202                                    rights++; /* but at least one rook lost them */
6203                         }
6204                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6205                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6206                                 rights++; 
6207                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6208                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6209                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6210                                    rights++;
6211                         }
6212                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6213                             && appData.drawRepeats > 1) {
6214                              /* adjudicate after user-specified nr of repeats */
6215                              if(engineOpponent) {
6216                                SendToProgram("force\n", engineOpponent); // suppress reply
6217                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6218                              }
6219                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6220                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6221                                 // [HGM] xiangqi: check for forbidden perpetuals
6222                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6223                                 for(m=forwardMostMove; m>k; m-=2) {
6224                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6225                                         ourPerpetual = 0; // the current mover did not always check
6226                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6227                                         hisPerpetual = 0; // the opponent did not always check
6228                                 }
6229                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6230                                                                         ourPerpetual, hisPerpetual);
6231                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6232                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6233                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6234                                     return 1;
6235                                 }
6236                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6237                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6238                                 // Now check for perpetual chases
6239                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6240                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6241                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6242                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6243                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6244                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6245                                         return 1;
6246                                     }
6247                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6248                                         break; // Abort repetition-checking loop.
6249                                 }
6250                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6251                              }
6252                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6253                              return 1;
6254                         }
6255                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6256                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6257                     }
6258                 }
6259
6260                 /* Now we test for 50-move draws. Determine ply count */
6261                 count = forwardMostMove;
6262                 /* look for last irreversble move */
6263                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6264                     count--;
6265                 /* if we hit starting position, add initial plies */
6266                 if( count == backwardMostMove )
6267                     count -= initialRulePlies;
6268                 count = forwardMostMove - count; 
6269                 if( count >= 100)
6270                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6271                          /* this is used to judge if draw claims are legal */
6272                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6273                          if(engineOpponent) {
6274                            SendToProgram("force\n", engineOpponent); // suppress reply
6275                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6276                          }
6277                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6278                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6279                          return 1;
6280                 }
6281
6282                 /* if draw offer is pending, treat it as a draw claim
6283                  * when draw condition present, to allow engines a way to
6284                  * claim draws before making their move to avoid a race
6285                  * condition occurring after their move
6286                  */
6287                 if(gameMode == TwoMachinesPlay) // for now; figure out how to handle claims in human games
6288                 if( cps->other->offeredDraw || cps->offeredDraw ) {
6289                          char *p = NULL;
6290                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6291                              p = "Draw claim: 50-move rule";
6292                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6293                              p = "Draw claim: 3-fold repetition";
6294                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6295                              p = "Draw claim: insufficient mating material";
6296                          if( p != NULL ) {
6297                              if(engineOpponent) {
6298                                SendToProgram("force\n", engineOpponent); // suppress reply
6299                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6300                              }
6301                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6302                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6303                              return 1;
6304                          }
6305                 }
6306
6307                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6308                     if(engineOpponent) {
6309                       SendToProgram("force\n", engineOpponent); // suppress reply
6310                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6311                     }
6312                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6313                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6314                     return 1;
6315                 }
6316         return 0;
6317 }
6318
6319 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6320 {   // [HGM] book: this routine intercepts moves to simulate book replies
6321     char *bookHit = NULL;
6322
6323     //first determine if the incoming move brings opponent into his book
6324     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6325         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6326     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6327     if(bookHit != NULL && !cps->bookSuspend) {
6328         // make sure opponent is not going to reply after receiving move to book position
6329         SendToProgram("force\n", cps);
6330         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6331     }
6332     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6333     // now arrange restart after book miss
6334     if(bookHit) {
6335         // after a book hit we never send 'go', and the code after the call to this routine
6336         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6337         char buf[MSG_SIZ];
6338         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6339         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6340         SendToProgram(buf, cps);
6341         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6342     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6343         SendToProgram("go\n", cps);
6344         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6345     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6346         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6347             SendToProgram("go\n", cps); 
6348         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6349     }
6350     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6351 }
6352
6353 char *savedMessage;
6354 ChessProgramState *savedState;
6355 void DeferredBookMove(void)
6356 {
6357         if(savedState->lastPing != savedState->lastPong)
6358                     ScheduleDelayedEvent(DeferredBookMove, 10);
6359         else
6360         HandleMachineMove(savedMessage, savedState);
6361 }
6362
6363 void
6364 HandleMachineMove(message, cps)
6365      char *message;
6366      ChessProgramState *cps;
6367 {
6368     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6369     char realname[MSG_SIZ];
6370     int fromX, fromY, toX, toY;
6371     ChessMove moveType;
6372     char promoChar;
6373     char *p;
6374     int machineWhite;
6375     char *bookHit;
6376
6377     cps->userError = 0;
6378
6379 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6380     /*
6381      * Kludge to ignore BEL characters
6382      */
6383     while (*message == '\007') message++;
6384
6385     /*
6386      * [HGM] engine debug message: ignore lines starting with '#' character
6387      */
6388     if(cps->debug && *message == '#') return;
6389
6390     /*
6391      * Look for book output
6392      */
6393     if (cps == &first && bookRequested) {
6394         if (message[0] == '\t' || message[0] == ' ') {
6395             /* Part of the book output is here; append it */
6396             strcat(bookOutput, message);
6397             strcat(bookOutput, "  \n");
6398             return;
6399         } else if (bookOutput[0] != NULLCHAR) {
6400             /* All of book output has arrived; display it */
6401             char *p = bookOutput;
6402             while (*p != NULLCHAR) {
6403                 if (*p == '\t') *p = ' ';
6404                 p++;
6405             }
6406             DisplayInformation(bookOutput);
6407             bookRequested = FALSE;
6408             /* Fall through to parse the current output */
6409         }
6410     }
6411
6412     /*
6413      * Look for machine move.
6414      */
6415     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6416         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6417     {
6418         /* This method is only useful on engines that support ping */
6419         if (cps->lastPing != cps->lastPong) {
6420           if (gameMode == BeginningOfGame) {
6421             /* Extra move from before last new; ignore */
6422             if (appData.debugMode) {
6423                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6424             }
6425           } else {
6426             if (appData.debugMode) {
6427                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6428                         cps->which, gameMode);
6429             }
6430
6431             SendToProgram("undo\n", cps);
6432           }
6433           return;
6434         }
6435
6436         switch (gameMode) {
6437           case BeginningOfGame:
6438             /* Extra move from before last reset; ignore */
6439             if (appData.debugMode) {
6440                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6441             }
6442             return;
6443
6444           case EndOfGame:
6445           case IcsIdle:
6446           default:
6447             /* Extra move after we tried to stop.  The mode test is
6448                not a reliable way of detecting this problem, but it's
6449                the best we can do on engines that don't support ping.
6450             */
6451             if (appData.debugMode) {
6452                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6453                         cps->which, gameMode);
6454             }
6455             SendToProgram("undo\n", cps);
6456             return;
6457
6458           case MachinePlaysWhite:
6459           case IcsPlayingWhite:
6460             machineWhite = TRUE;
6461             break;
6462
6463           case MachinePlaysBlack:
6464           case IcsPlayingBlack:
6465             machineWhite = FALSE;
6466             break;
6467
6468           case TwoMachinesPlay:
6469             machineWhite = (cps->twoMachinesColor[0] == 'w');
6470             break;
6471         }
6472         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6473             if (appData.debugMode) {
6474                 fprintf(debugFP,
6475                         "Ignoring move out of turn by %s, gameMode %d"
6476                         ", forwardMost %d\n",
6477                         cps->which, gameMode, forwardMostMove);
6478             }
6479             return;
6480         }
6481
6482     if (appData.debugMode) { int f = forwardMostMove;
6483         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6484                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6485                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6486     }
6487         if(cps->alphaRank) AlphaRank(machineMove, 4);
6488         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6489                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6490             /* Machine move could not be parsed; ignore it. */
6491             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6492                     machineMove, cps->which);
6493             DisplayError(buf1, 0);
6494             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6495                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6496             if (gameMode == TwoMachinesPlay) {
6497               GameEnds(machineWhite ? BlackWins : WhiteWins,
6498                        buf1, GE_XBOARD);
6499             }
6500             return;
6501         }
6502
6503         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6504         /* So we have to redo legality test with true e.p. status here,  */
6505         /* to make sure an illegal e.p. capture does not slip through,   */
6506         /* to cause a forfeit on a justified illegal-move complaint      */
6507         /* of the opponent.                                              */
6508         if( gameMode==TwoMachinesPlay && appData.testLegality
6509             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6510                                                               ) {
6511            ChessMove moveType;
6512            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6513                              fromY, fromX, toY, toX, promoChar);
6514             if (appData.debugMode) {
6515                 int i;
6516                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6517                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6518                 fprintf(debugFP, "castling rights\n");
6519             }
6520             if(moveType == IllegalMove) {
6521                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6522                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6523                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6524                            buf1, GE_XBOARD);
6525                 return;
6526            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6527            /* [HGM] Kludge to handle engines that send FRC-style castling
6528               when they shouldn't (like TSCP-Gothic) */
6529            switch(moveType) {
6530              case WhiteASideCastleFR:
6531              case BlackASideCastleFR:
6532                toX+=2;
6533                currentMoveString[2]++;
6534                break;
6535              case WhiteHSideCastleFR:
6536              case BlackHSideCastleFR:
6537                toX--;
6538                currentMoveString[2]--;
6539                break;
6540              default: ; // nothing to do, but suppresses warning of pedantic compilers
6541            }
6542         }
6543         hintRequested = FALSE;
6544         lastHint[0] = NULLCHAR;
6545         bookRequested = FALSE;
6546         /* Program may be pondering now */
6547         cps->maybeThinking = TRUE;
6548         if (cps->sendTime == 2) cps->sendTime = 1;
6549         if (cps->offeredDraw) cps->offeredDraw--;
6550
6551 #if ZIPPY
6552         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6553             first.initDone) {
6554           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6555           ics_user_moved = 1;
6556           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6557                 char buf[3*MSG_SIZ];
6558
6559                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6560                         programStats.score / 100.,
6561                         programStats.depth,
6562                         programStats.time / 100.,
6563                         (unsigned int)programStats.nodes,
6564                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6565                         programStats.movelist);
6566                 SendToICS(buf);
6567 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6568           }
6569         }
6570 #endif
6571         /* currentMoveString is set as a side-effect of ParseOneMove */
6572         strcpy(machineMove, currentMoveString);
6573         strcat(machineMove, "\n");
6574         strcpy(moveList[forwardMostMove], machineMove);
6575
6576         /* [AS] Save move info and clear stats for next move */
6577         pvInfoList[ forwardMostMove ].score = programStats.score;
6578         pvInfoList[ forwardMostMove ].depth = programStats.depth;
6579         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6580         ClearProgramStats();
6581         thinkOutput[0] = NULLCHAR;
6582         hiddenThinkOutputState = 0;
6583
6584         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6585
6586         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6587         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6588             int count = 0;
6589
6590             while( count < adjudicateLossPlies ) {
6591                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6592
6593                 if( count & 1 ) {
6594                     score = -score; /* Flip score for winning side */
6595                 }
6596
6597                 if( score > adjudicateLossThreshold ) {
6598                     break;
6599                 }
6600
6601                 count++;
6602             }
6603
6604             if( count >= adjudicateLossPlies ) {
6605                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6606
6607                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6608                     "Xboard adjudication", 
6609                     GE_XBOARD );
6610
6611                 return;
6612             }
6613         }
6614
6615         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6616
6617         bookHit = NULL;
6618         if (gameMode == TwoMachinesPlay) {
6619             /* [HGM] relaying draw offers moved to after reception of move */
6620             /* and interpreting offer as claim if it brings draw condition */
6621             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6622                 SendToProgram("draw\n", cps->other);
6623             }
6624             if (cps->other->sendTime) {
6625                 SendTimeRemaining(cps->other,
6626                                   cps->other->twoMachinesColor[0] == 'w');
6627             }
6628             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6629             if (firstMove && !bookHit) {
6630                 firstMove = FALSE;
6631                 if (cps->other->useColors) {
6632                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6633                 }
6634                 SendToProgram("go\n", cps->other);
6635             }
6636             cps->other->maybeThinking = TRUE;
6637         }
6638
6639         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6640         
6641         if (!pausing && appData.ringBellAfterMoves) {
6642             RingBell();
6643         }
6644
6645         /* 
6646          * Reenable menu items that were disabled while
6647          * machine was thinking
6648          */
6649         if (gameMode != TwoMachinesPlay)
6650             SetUserThinkingEnables();
6651
6652         // [HGM] book: after book hit opponent has received move and is now in force mode
6653         // force the book reply into it, and then fake that it outputted this move by jumping
6654         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6655         if(bookHit) {
6656                 static char bookMove[MSG_SIZ]; // a bit generous?
6657
6658                 strcpy(bookMove, "move ");
6659                 strcat(bookMove, bookHit);
6660                 message = bookMove;
6661                 cps = cps->other;
6662                 programStats.nodes = programStats.depth = programStats.time = 
6663                 programStats.score = programStats.got_only_move = 0;
6664                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6665
6666                 if(cps->lastPing != cps->lastPong) {
6667                     savedMessage = message; // args for deferred call
6668                     savedState = cps;
6669                     ScheduleDelayedEvent(DeferredBookMove, 10);
6670                     return;
6671                 }
6672                 goto FakeBookMove;
6673         }
6674
6675         return;
6676     }
6677
6678     /* Set special modes for chess engines.  Later something general
6679      *  could be added here; for now there is just one kludge feature,
6680      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6681      *  when "xboard" is given as an interactive command.
6682      */
6683     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6684         cps->useSigint = FALSE;
6685         cps->useSigterm = FALSE;
6686     }
6687     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6688       ParseFeatures(message+8, cps);
6689       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6690     }
6691
6692     /* [HGM] Allow engine to set up a position. Don't ask me why one would
6693      * want this, I was asked to put it in, and obliged.
6694      */
6695     if (!strncmp(message, "setboard ", 9)) {
6696         Board initial_position;
6697
6698         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6699
6700         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6701             DisplayError(_("Bad FEN received from engine"), 0);
6702             return ;
6703         } else {
6704            Reset(TRUE, FALSE);
6705            CopyBoard(boards[0], initial_position);
6706            initialRulePlies = FENrulePlies;
6707            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6708            else gameMode = MachinePlaysBlack;                 
6709            DrawPosition(FALSE, boards[currentMove]);
6710         }
6711         return;
6712     }
6713
6714     /*
6715      * Look for communication commands
6716      */
6717     if (!strncmp(message, "telluser ", 9)) {
6718         DisplayNote(message + 9);
6719         return;
6720     }
6721     if (!strncmp(message, "tellusererror ", 14)) {
6722         cps->userError = 1;
6723         DisplayError(message + 14, 0);
6724         return;
6725     }
6726     if (!strncmp(message, "tellopponent ", 13)) {
6727       if (appData.icsActive) {
6728         if (loggedOn) {
6729           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6730           SendToICS(buf1);
6731         }
6732       } else {
6733         DisplayNote(message + 13);
6734       }
6735       return;
6736     }
6737     if (!strncmp(message, "tellothers ", 11)) {
6738       if (appData.icsActive) {
6739         if (loggedOn) {
6740           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6741           SendToICS(buf1);
6742         }
6743       }
6744       return;
6745     }
6746     if (!strncmp(message, "tellall ", 8)) {
6747       if (appData.icsActive) {
6748         if (loggedOn) {
6749           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6750           SendToICS(buf1);
6751         }
6752       } else {
6753         DisplayNote(message + 8);
6754       }
6755       return;
6756     }
6757     if (strncmp(message, "warning", 7) == 0) {
6758         /* Undocumented feature, use tellusererror in new code */
6759         DisplayError(message, 0);
6760         return;
6761     }
6762     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6763         strcpy(realname, cps->tidy);
6764         strcat(realname, " query");
6765         AskQuestion(realname, buf2, buf1, cps->pr);
6766         return;
6767     }
6768     /* Commands from the engine directly to ICS.  We don't allow these to be 
6769      *  sent until we are logged on. Crafty kibitzes have been known to 
6770      *  interfere with the login process.
6771      */
6772     if (loggedOn) {
6773         if (!strncmp(message, "tellics ", 8)) {
6774             SendToICS(message + 8);
6775             SendToICS("\n");
6776             return;
6777         }
6778         if (!strncmp(message, "tellicsnoalias ", 15)) {
6779             SendToICS(ics_prefix);
6780             SendToICS(message + 15);
6781             SendToICS("\n");
6782             return;
6783         }
6784         /* The following are for backward compatibility only */
6785         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6786             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6787             SendToICS(ics_prefix);
6788             SendToICS(message);
6789             SendToICS("\n");
6790             return;
6791         }
6792     }
6793     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6794         return;
6795     }
6796     /*
6797      * If the move is illegal, cancel it and redraw the board.
6798      * Also deal with other error cases.  Matching is rather loose
6799      * here to accommodate engines written before the spec.
6800      */
6801     if (strncmp(message + 1, "llegal move", 11) == 0 ||
6802         strncmp(message, "Error", 5) == 0) {
6803         if (StrStr(message, "name") || 
6804             StrStr(message, "rating") || StrStr(message, "?") ||
6805             StrStr(message, "result") || StrStr(message, "board") ||
6806             StrStr(message, "bk") || StrStr(message, "computer") ||
6807             StrStr(message, "variant") || StrStr(message, "hint") ||
6808             StrStr(message, "random") || StrStr(message, "depth") ||
6809             StrStr(message, "accepted")) {
6810             return;
6811         }
6812         if (StrStr(message, "protover")) {
6813           /* Program is responding to input, so it's apparently done
6814              initializing, and this error message indicates it is
6815              protocol version 1.  So we don't need to wait any longer
6816              for it to initialize and send feature commands. */
6817           FeatureDone(cps, 1);
6818           cps->protocolVersion = 1;
6819           return;
6820         }
6821         cps->maybeThinking = FALSE;
6822
6823         if (StrStr(message, "draw")) {
6824             /* Program doesn't have "draw" command */
6825             cps->sendDrawOffers = 0;
6826             return;
6827         }
6828         if (cps->sendTime != 1 &&
6829             (StrStr(message, "time") || StrStr(message, "otim"))) {
6830           /* Program apparently doesn't have "time" or "otim" command */
6831           cps->sendTime = 0;
6832           return;
6833         }
6834         if (StrStr(message, "analyze")) {
6835             cps->analysisSupport = FALSE;
6836             cps->analyzing = FALSE;
6837             Reset(FALSE, TRUE);
6838             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6839             DisplayError(buf2, 0);
6840             return;
6841         }
6842         if (StrStr(message, "(no matching move)st")) {
6843           /* Special kludge for GNU Chess 4 only */
6844           cps->stKludge = TRUE;
6845           SendTimeControl(cps, movesPerSession, timeControl,
6846                           timeIncrement, appData.searchDepth,
6847                           searchTime);
6848           return;
6849         }
6850         if (StrStr(message, "(no matching move)sd")) {
6851           /* Special kludge for GNU Chess 4 only */
6852           cps->sdKludge = TRUE;
6853           SendTimeControl(cps, movesPerSession, timeControl,
6854                           timeIncrement, appData.searchDepth,
6855                           searchTime);
6856           return;
6857         }
6858         if (!StrStr(message, "llegal")) {
6859             return;
6860         }
6861         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6862             gameMode == IcsIdle) return;
6863         if (forwardMostMove <= backwardMostMove) return;
6864         if (pausing) PauseEvent();
6865       if(appData.forceIllegal) {
6866             // [HGM] illegal: machine refused move; force position after move into it
6867           SendToProgram("force\n", cps);
6868           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6869                 // we have a real problem now, as SendBoard will use the a2a3 kludge
6870                 // when black is to move, while there might be nothing on a2 or black
6871                 // might already have the move. So send the board as if white has the move.
6872                 // But first we must change the stm of the engine, as it refused the last move
6873                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6874                 if(WhiteOnMove(forwardMostMove)) {
6875                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
6876                     SendBoard(cps, forwardMostMove); // kludgeless board
6877                 } else {
6878                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
6879                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6880                     SendBoard(cps, forwardMostMove+1); // kludgeless board
6881                 }
6882           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6883             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6884                  gameMode == TwoMachinesPlay)
6885               SendToProgram("go\n", cps);
6886             return;
6887       } else
6888         if (gameMode == PlayFromGameFile) {
6889             /* Stop reading this game file */
6890             gameMode = EditGame;
6891             ModeHighlight();
6892         }
6893         currentMove = --forwardMostMove;
6894         DisplayMove(currentMove-1); /* before DisplayMoveError */
6895         SwitchClocks();
6896         DisplayBothClocks();
6897         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6898                 parseList[currentMove], cps->which);
6899         DisplayMoveError(buf1);
6900         DrawPosition(FALSE, boards[currentMove]);
6901
6902         /* [HGM] illegal-move claim should forfeit game when Xboard */
6903         /* only passes fully legal moves                            */
6904         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6905             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6906                                 "False illegal-move claim", GE_XBOARD );
6907         }
6908         return;
6909     }
6910     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6911         /* Program has a broken "time" command that
6912            outputs a string not ending in newline.
6913            Don't use it. */
6914         cps->sendTime = 0;
6915     }
6916     
6917     /*
6918      * If chess program startup fails, exit with an error message.
6919      * Attempts to recover here are futile.
6920      */
6921     if ((StrStr(message, "unknown host") != NULL)
6922         || (StrStr(message, "No remote directory") != NULL)
6923         || (StrStr(message, "not found") != NULL)
6924         || (StrStr(message, "No such file") != NULL)
6925         || (StrStr(message, "can't alloc") != NULL)
6926         || (StrStr(message, "Permission denied") != NULL)) {
6927
6928         cps->maybeThinking = FALSE;
6929         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6930                 cps->which, cps->program, cps->host, message);
6931         RemoveInputSource(cps->isr);
6932         DisplayFatalError(buf1, 0, 1);
6933         return;
6934     }
6935     
6936     /* 
6937      * Look for hint output
6938      */
6939     if (sscanf(message, "Hint: %s", buf1) == 1) {
6940         if (cps == &first && hintRequested) {
6941             hintRequested = FALSE;
6942             if (ParseOneMove(buf1, forwardMostMove, &moveType,
6943                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
6944                 (void) CoordsToAlgebraic(boards[forwardMostMove],
6945                                     PosFlags(forwardMostMove),
6946                                     fromY, fromX, toY, toX, promoChar, buf1);
6947                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6948                 DisplayInformation(buf2);
6949             } else {
6950                 /* Hint move could not be parsed!? */
6951               snprintf(buf2, sizeof(buf2),
6952                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
6953                         buf1, cps->which);
6954                 DisplayError(buf2, 0);
6955             }
6956         } else {
6957             strcpy(lastHint, buf1);
6958         }
6959         return;
6960     }
6961
6962     /*
6963      * Ignore other messages if game is not in progress
6964      */
6965     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6966         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6967
6968     /*
6969      * look for win, lose, draw, or draw offer
6970      */
6971     if (strncmp(message, "1-0", 3) == 0) {
6972         char *p, *q, *r = "";
6973         p = strchr(message, '{');
6974         if (p) {
6975             q = strchr(p, '}');
6976             if (q) {
6977                 *q = NULLCHAR;
6978                 r = p + 1;
6979             }
6980         }
6981         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6982         return;
6983     } else if (strncmp(message, "0-1", 3) == 0) {
6984         char *p, *q, *r = "";
6985         p = strchr(message, '{');
6986         if (p) {
6987             q = strchr(p, '}');
6988             if (q) {
6989                 *q = NULLCHAR;
6990                 r = p + 1;
6991             }
6992         }
6993         /* Kludge for Arasan 4.1 bug */
6994         if (strcmp(r, "Black resigns") == 0) {
6995             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6996             return;
6997         }
6998         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6999         return;
7000     } else if (strncmp(message, "1/2", 3) == 0) {
7001         char *p, *q, *r = "";
7002         p = strchr(message, '{');
7003         if (p) {
7004             q = strchr(p, '}');
7005             if (q) {
7006                 *q = NULLCHAR;
7007                 r = p + 1;
7008             }
7009         }
7010             
7011         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7012         return;
7013
7014     } else if (strncmp(message, "White resign", 12) == 0) {
7015         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7016         return;
7017     } else if (strncmp(message, "Black resign", 12) == 0) {
7018         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7019         return;
7020     } else if (strncmp(message, "White matches", 13) == 0 ||
7021                strncmp(message, "Black matches", 13) == 0   ) {
7022         /* [HGM] ignore GNUShogi noises */
7023         return;
7024     } else if (strncmp(message, "White", 5) == 0 &&
7025                message[5] != '(' &&
7026                StrStr(message, "Black") == NULL) {
7027         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7028         return;
7029     } else if (strncmp(message, "Black", 5) == 0 &&
7030                message[5] != '(') {
7031         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7032         return;
7033     } else if (strcmp(message, "resign") == 0 ||
7034                strcmp(message, "computer resigns") == 0) {
7035         switch (gameMode) {
7036           case MachinePlaysBlack:
7037           case IcsPlayingBlack:
7038             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7039             break;
7040           case MachinePlaysWhite:
7041           case IcsPlayingWhite:
7042             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7043             break;
7044           case TwoMachinesPlay:
7045             if (cps->twoMachinesColor[0] == 'w')
7046               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7047             else
7048               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7049             break;
7050           default:
7051             /* can't happen */
7052             break;
7053         }
7054         return;
7055     } else if (strncmp(message, "opponent mates", 14) == 0) {
7056         switch (gameMode) {
7057           case MachinePlaysBlack:
7058           case IcsPlayingBlack:
7059             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7060             break;
7061           case MachinePlaysWhite:
7062           case IcsPlayingWhite:
7063             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7064             break;
7065           case TwoMachinesPlay:
7066             if (cps->twoMachinesColor[0] == 'w')
7067               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7068             else
7069               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7070             break;
7071           default:
7072             /* can't happen */
7073             break;
7074         }
7075         return;
7076     } else if (strncmp(message, "computer mates", 14) == 0) {
7077         switch (gameMode) {
7078           case MachinePlaysBlack:
7079           case IcsPlayingBlack:
7080             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7081             break;
7082           case MachinePlaysWhite:
7083           case IcsPlayingWhite:
7084             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7085             break;
7086           case TwoMachinesPlay:
7087             if (cps->twoMachinesColor[0] == 'w')
7088               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7089             else
7090               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7091             break;
7092           default:
7093             /* can't happen */
7094             break;
7095         }
7096         return;
7097     } else if (strncmp(message, "checkmate", 9) == 0) {
7098         if (WhiteOnMove(forwardMostMove)) {
7099             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7100         } else {
7101             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7102         }
7103         return;
7104     } else if (strstr(message, "Draw") != NULL ||
7105                strstr(message, "game is a draw") != NULL) {
7106         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7107         return;
7108     } else if (strstr(message, "offer") != NULL &&
7109                strstr(message, "draw") != NULL) {
7110 #if ZIPPY
7111         if (appData.zippyPlay && first.initDone) {
7112             /* Relay offer to ICS */
7113             SendToICS(ics_prefix);
7114             SendToICS("draw\n");
7115         }
7116 #endif
7117         cps->offeredDraw = 2; /* valid until this engine moves twice */
7118         if (gameMode == TwoMachinesPlay) {
7119             if (cps->other->offeredDraw) {
7120                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7121             /* [HGM] in two-machine mode we delay relaying draw offer      */
7122             /* until after we also have move, to see if it is really claim */
7123             }
7124         } else if (gameMode == MachinePlaysWhite ||
7125                    gameMode == MachinePlaysBlack) {
7126           if (userOfferedDraw) {
7127             DisplayInformation(_("Machine accepts your draw offer"));
7128             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7129           } else {
7130             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7131           }
7132         }
7133     }
7134
7135     
7136     /*
7137      * Look for thinking output
7138      */
7139     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7140           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7141                                 ) {
7142         int plylev, mvleft, mvtot, curscore, time;
7143         char mvname[MOVE_LEN];
7144         u64 nodes; // [DM]
7145         char plyext;
7146         int ignore = FALSE;
7147         int prefixHint = FALSE;
7148         mvname[0] = NULLCHAR;
7149
7150         switch (gameMode) {
7151           case MachinePlaysBlack:
7152           case IcsPlayingBlack:
7153             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7154             break;
7155           case MachinePlaysWhite:
7156           case IcsPlayingWhite:
7157             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7158             break;
7159           case AnalyzeMode:
7160           case AnalyzeFile:
7161             break;
7162           case IcsObserving: /* [DM] icsEngineAnalyze */
7163             if (!appData.icsEngineAnalyze) ignore = TRUE;
7164             break;
7165           case TwoMachinesPlay:
7166             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7167                 ignore = TRUE;
7168             }
7169             break;
7170           default:
7171             ignore = TRUE;
7172             break;
7173         }
7174
7175         if (!ignore) {
7176             buf1[0] = NULLCHAR;
7177             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7178                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7179
7180                 if (plyext != ' ' && plyext != '\t') {
7181                     time *= 100;
7182                 }
7183
7184                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7185                 if( cps->scoreIsAbsolute && 
7186                     ( gameMode == MachinePlaysBlack ||
7187                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7188                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7189                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7190                      !WhiteOnMove(currentMove)
7191                     ) )
7192                 {
7193                     curscore = -curscore;
7194                 }
7195
7196
7197                 programStats.depth = plylev;
7198                 programStats.nodes = nodes;
7199                 programStats.time = time;
7200                 programStats.score = curscore;
7201                 programStats.got_only_move = 0;
7202
7203                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7204                         int ticklen;
7205
7206                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7207                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7208                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7209                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7210                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7211                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7212                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7213                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7214                 }
7215
7216                 /* Buffer overflow protection */
7217                 if (buf1[0] != NULLCHAR) {
7218                     if (strlen(buf1) >= sizeof(programStats.movelist)
7219                         && appData.debugMode) {
7220                         fprintf(debugFP,
7221                                 "PV is too long; using the first %u bytes.\n",
7222                                 (unsigned) sizeof(programStats.movelist) - 1);
7223                     }
7224
7225                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7226                 } else {
7227                     sprintf(programStats.movelist, " no PV\n");
7228                 }
7229
7230                 if (programStats.seen_stat) {
7231                     programStats.ok_to_send = 1;
7232                 }
7233
7234                 if (strchr(programStats.movelist, '(') != NULL) {
7235                     programStats.line_is_book = 1;
7236                     programStats.nr_moves = 0;
7237                     programStats.moves_left = 0;
7238                 } else {
7239                     programStats.line_is_book = 0;
7240                 }
7241
7242                 SendProgramStatsToFrontend( cps, &programStats );
7243
7244                 /* 
7245                     [AS] Protect the thinkOutput buffer from overflow... this
7246                     is only useful if buf1 hasn't overflowed first!
7247                 */
7248                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7249                         plylev, 
7250                         (gameMode == TwoMachinesPlay ?
7251                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7252                         ((double) curscore) / 100.0,
7253                         prefixHint ? lastHint : "",
7254                         prefixHint ? " " : "" );
7255
7256                 if( buf1[0] != NULLCHAR ) {
7257                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7258
7259                     if( strlen(buf1) > max_len ) {
7260                         if( appData.debugMode) {
7261                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7262                         }
7263                         buf1[max_len+1] = '\0';
7264                     }
7265
7266                     strcat( thinkOutput, buf1 );
7267                 }
7268
7269                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7270                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7271                     DisplayMove(currentMove - 1);
7272                 }
7273                 return;
7274
7275             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7276                 /* crafty (9.25+) says "(only move) <move>"
7277                  * if there is only 1 legal move
7278                  */
7279                 sscanf(p, "(only move) %s", buf1);
7280                 sprintf(thinkOutput, "%s (only move)", buf1);
7281                 sprintf(programStats.movelist, "%s (only move)", buf1);
7282                 programStats.depth = 1;
7283                 programStats.nr_moves = 1;
7284                 programStats.moves_left = 1;
7285                 programStats.nodes = 1;
7286                 programStats.time = 1;
7287                 programStats.got_only_move = 1;
7288
7289                 /* Not really, but we also use this member to
7290                    mean "line isn't going to change" (Crafty
7291                    isn't searching, so stats won't change) */
7292                 programStats.line_is_book = 1;
7293
7294                 SendProgramStatsToFrontend( cps, &programStats );
7295                 
7296                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7297                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7298                     DisplayMove(currentMove - 1);
7299                 }
7300                 return;
7301             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7302                               &time, &nodes, &plylev, &mvleft,
7303                               &mvtot, mvname) >= 5) {
7304                 /* The stat01: line is from Crafty (9.29+) in response
7305                    to the "." command */
7306                 programStats.seen_stat = 1;
7307                 cps->maybeThinking = TRUE;
7308
7309                 if (programStats.got_only_move || !appData.periodicUpdates)
7310                   return;
7311
7312                 programStats.depth = plylev;
7313                 programStats.time = time;
7314                 programStats.nodes = nodes;
7315                 programStats.moves_left = mvleft;
7316                 programStats.nr_moves = mvtot;
7317                 strcpy(programStats.move_name, mvname);
7318                 programStats.ok_to_send = 1;
7319                 programStats.movelist[0] = '\0';
7320
7321                 SendProgramStatsToFrontend( cps, &programStats );
7322
7323                 return;
7324
7325             } else if (strncmp(message,"++",2) == 0) {
7326                 /* Crafty 9.29+ outputs this */
7327                 programStats.got_fail = 2;
7328                 return;
7329
7330             } else if (strncmp(message,"--",2) == 0) {
7331                 /* Crafty 9.29+ outputs this */
7332                 programStats.got_fail = 1;
7333                 return;
7334
7335             } else if (thinkOutput[0] != NULLCHAR &&
7336                        strncmp(message, "    ", 4) == 0) {
7337                 unsigned message_len;
7338
7339                 p = message;
7340                 while (*p && *p == ' ') p++;
7341
7342                 message_len = strlen( p );
7343
7344                 /* [AS] Avoid buffer overflow */
7345                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7346                     strcat(thinkOutput, " ");
7347                     strcat(thinkOutput, p);
7348                 }
7349
7350                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7351                     strcat(programStats.movelist, " ");
7352                     strcat(programStats.movelist, p);
7353                 }
7354
7355                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7356                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7357                     DisplayMove(currentMove - 1);
7358                 }
7359                 return;
7360             }
7361         }
7362         else {
7363             buf1[0] = NULLCHAR;
7364
7365             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7366                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7367             {
7368                 ChessProgramStats cpstats;
7369
7370                 if (plyext != ' ' && plyext != '\t') {
7371                     time *= 100;
7372                 }
7373
7374                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7375                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7376                     curscore = -curscore;
7377                 }
7378
7379                 cpstats.depth = plylev;
7380                 cpstats.nodes = nodes;
7381                 cpstats.time = time;
7382                 cpstats.score = curscore;
7383                 cpstats.got_only_move = 0;
7384                 cpstats.movelist[0] = '\0';
7385
7386                 if (buf1[0] != NULLCHAR) {
7387                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7388                 }
7389
7390                 cpstats.ok_to_send = 0;
7391                 cpstats.line_is_book = 0;
7392                 cpstats.nr_moves = 0;
7393                 cpstats.moves_left = 0;
7394
7395                 SendProgramStatsToFrontend( cps, &cpstats );
7396             }
7397         }
7398     }
7399 }
7400
7401
7402 /* Parse a game score from the character string "game", and
7403    record it as the history of the current game.  The game
7404    score is NOT assumed to start from the standard position. 
7405    The display is not updated in any way.
7406    */
7407 void
7408 ParseGameHistory(game)
7409      char *game;
7410 {
7411     ChessMove moveType;
7412     int fromX, fromY, toX, toY, boardIndex;
7413     char promoChar;
7414     char *p, *q;
7415     char buf[MSG_SIZ];
7416
7417     if (appData.debugMode)
7418       fprintf(debugFP, "Parsing game history: %s\n", game);
7419
7420     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7421     gameInfo.site = StrSave(appData.icsHost);
7422     gameInfo.date = PGNDate();
7423     gameInfo.round = StrSave("-");
7424
7425     /* Parse out names of players */
7426     while (*game == ' ') game++;
7427     p = buf;
7428     while (*game != ' ') *p++ = *game++;
7429     *p = NULLCHAR;
7430     gameInfo.white = StrSave(buf);
7431     while (*game == ' ') game++;
7432     p = buf;
7433     while (*game != ' ' && *game != '\n') *p++ = *game++;
7434     *p = NULLCHAR;
7435     gameInfo.black = StrSave(buf);
7436
7437     /* Parse moves */
7438     boardIndex = blackPlaysFirst ? 1 : 0;
7439     yynewstr(game);
7440     for (;;) {
7441         yyboardindex = boardIndex;
7442         moveType = (ChessMove) yylex();
7443         switch (moveType) {
7444           case IllegalMove:             /* maybe suicide chess, etc. */
7445   if (appData.debugMode) {
7446     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7447     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7448     setbuf(debugFP, NULL);
7449   }
7450           case WhitePromotionChancellor:
7451           case BlackPromotionChancellor:
7452           case WhitePromotionArchbishop:
7453           case BlackPromotionArchbishop:
7454           case WhitePromotionQueen:
7455           case BlackPromotionQueen:
7456           case WhitePromotionRook:
7457           case BlackPromotionRook:
7458           case WhitePromotionBishop:
7459           case BlackPromotionBishop:
7460           case WhitePromotionKnight:
7461           case BlackPromotionKnight:
7462           case WhitePromotionKing:
7463           case BlackPromotionKing:
7464           case NormalMove:
7465           case WhiteCapturesEnPassant:
7466           case BlackCapturesEnPassant:
7467           case WhiteKingSideCastle:
7468           case WhiteQueenSideCastle:
7469           case BlackKingSideCastle:
7470           case BlackQueenSideCastle:
7471           case WhiteKingSideCastleWild:
7472           case WhiteQueenSideCastleWild:
7473           case BlackKingSideCastleWild:
7474           case BlackQueenSideCastleWild:
7475           /* PUSH Fabien */
7476           case WhiteHSideCastleFR:
7477           case WhiteASideCastleFR:
7478           case BlackHSideCastleFR:
7479           case BlackASideCastleFR:
7480           /* POP Fabien */
7481             fromX = currentMoveString[0] - AAA;
7482             fromY = currentMoveString[1] - ONE;
7483             toX = currentMoveString[2] - AAA;
7484             toY = currentMoveString[3] - ONE;
7485             promoChar = currentMoveString[4];
7486             break;
7487           case WhiteDrop:
7488           case BlackDrop:
7489             fromX = moveType == WhiteDrop ?
7490               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7491             (int) CharToPiece(ToLower(currentMoveString[0]));
7492             fromY = DROP_RANK;
7493             toX = currentMoveString[2] - AAA;
7494             toY = currentMoveString[3] - ONE;
7495             promoChar = NULLCHAR;
7496             break;
7497           case AmbiguousMove:
7498             /* bug? */
7499             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7500   if (appData.debugMode) {
7501     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7502     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7503     setbuf(debugFP, NULL);
7504   }
7505             DisplayError(buf, 0);
7506             return;
7507           case ImpossibleMove:
7508             /* bug? */
7509             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7510   if (appData.debugMode) {
7511     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7512     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7513     setbuf(debugFP, NULL);
7514   }
7515             DisplayError(buf, 0);
7516             return;
7517           case (ChessMove) 0:   /* end of file */
7518             if (boardIndex < backwardMostMove) {
7519                 /* Oops, gap.  How did that happen? */
7520                 DisplayError(_("Gap in move list"), 0);
7521                 return;
7522             }
7523             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7524             if (boardIndex > forwardMostMove) {
7525                 forwardMostMove = boardIndex;
7526             }
7527             return;
7528           case ElapsedTime:
7529             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7530                 strcat(parseList[boardIndex-1], " ");
7531                 strcat(parseList[boardIndex-1], yy_text);
7532             }
7533             continue;
7534           case Comment:
7535           case PGNTag:
7536           case NAG:
7537           default:
7538             /* ignore */
7539             continue;
7540           case WhiteWins:
7541           case BlackWins:
7542           case GameIsDrawn:
7543           case GameUnfinished:
7544             if (gameMode == IcsExamining) {
7545                 if (boardIndex < backwardMostMove) {
7546                     /* Oops, gap.  How did that happen? */
7547                     return;
7548                 }
7549                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7550                 return;
7551             }
7552             gameInfo.result = moveType;
7553             p = strchr(yy_text, '{');
7554             if (p == NULL) p = strchr(yy_text, '(');
7555             if (p == NULL) {
7556                 p = yy_text;
7557                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7558             } else {
7559                 q = strchr(p, *p == '{' ? '}' : ')');
7560                 if (q != NULL) *q = NULLCHAR;
7561                 p++;
7562             }
7563             gameInfo.resultDetails = StrSave(p);
7564             continue;
7565         }
7566         if (boardIndex >= forwardMostMove &&
7567             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7568             backwardMostMove = blackPlaysFirst ? 1 : 0;
7569             return;
7570         }
7571         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7572                                  fromY, fromX, toY, toX, promoChar,
7573                                  parseList[boardIndex]);
7574         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7575         /* currentMoveString is set as a side-effect of yylex */
7576         strcpy(moveList[boardIndex], currentMoveString);
7577         strcat(moveList[boardIndex], "\n");
7578         boardIndex++;
7579         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7580         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7581           case MT_NONE:
7582           case MT_STALEMATE:
7583           default:
7584             break;
7585           case MT_CHECK:
7586             if(gameInfo.variant != VariantShogi)
7587                 strcat(parseList[boardIndex - 1], "+");
7588             break;
7589           case MT_CHECKMATE:
7590           case MT_STAINMATE:
7591             strcat(parseList[boardIndex - 1], "#");
7592             break;
7593         }
7594     }
7595 }
7596
7597
7598 /* Apply a move to the given board  */
7599 void
7600 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7601      int fromX, fromY, toX, toY;
7602      int promoChar;
7603      Board board;
7604 {
7605   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7606   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7607
7608     /* [HGM] compute & store e.p. status and castling rights for new position */
7609     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7610     { int i;
7611
7612       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7613       oldEP = (signed char)board[EP_STATUS];
7614       board[EP_STATUS] = EP_NONE;
7615
7616       if( board[toY][toX] != EmptySquare ) 
7617            board[EP_STATUS] = EP_CAPTURE;  
7618
7619       if( board[fromY][fromX] == WhitePawn ) {
7620            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7621                board[EP_STATUS] = EP_PAWN_MOVE;
7622            if( toY-fromY==2) {
7623                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7624                         gameInfo.variant != VariantBerolina || toX < fromX)
7625                       board[EP_STATUS] = toX | berolina;
7626                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7627                         gameInfo.variant != VariantBerolina || toX > fromX) 
7628                       board[EP_STATUS] = toX;
7629            }
7630       } else 
7631       if( board[fromY][fromX] == BlackPawn ) {
7632            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7633                board[EP_STATUS] = EP_PAWN_MOVE; 
7634            if( toY-fromY== -2) {
7635                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7636                         gameInfo.variant != VariantBerolina || toX < fromX)
7637                       board[EP_STATUS] = toX | berolina;
7638                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7639                         gameInfo.variant != VariantBerolina || toX > fromX) 
7640                       board[EP_STATUS] = toX;
7641            }
7642        }
7643
7644        for(i=0; i<nrCastlingRights; i++) {
7645            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7646               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7647              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7648        }
7649
7650     }
7651
7652   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7653   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7654        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7655          
7656   if (fromX == toX && fromY == toY) return;
7657
7658   if (fromY == DROP_RANK) {
7659         /* must be first */
7660         piece = board[toY][toX] = (ChessSquare) fromX;
7661   } else {
7662      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7663      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7664      if(gameInfo.variant == VariantKnightmate)
7665          king += (int) WhiteUnicorn - (int) WhiteKing;
7666
7667     /* Code added by Tord: */
7668     /* FRC castling assumed when king captures friendly rook. */
7669     if (board[fromY][fromX] == WhiteKing &&
7670              board[toY][toX] == WhiteRook) {
7671       board[fromY][fromX] = EmptySquare;
7672       board[toY][toX] = EmptySquare;
7673       if(toX > fromX) {
7674         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7675       } else {
7676         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7677       }
7678     } else if (board[fromY][fromX] == BlackKing &&
7679                board[toY][toX] == BlackRook) {
7680       board[fromY][fromX] = EmptySquare;
7681       board[toY][toX] = EmptySquare;
7682       if(toX > fromX) {
7683         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7684       } else {
7685         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7686       }
7687     /* End of code added by Tord */
7688
7689     } else if (board[fromY][fromX] == king
7690         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7691         && toY == fromY && toX > fromX+1) {
7692         board[fromY][fromX] = EmptySquare;
7693         board[toY][toX] = king;
7694         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7695         board[fromY][BOARD_RGHT-1] = EmptySquare;
7696     } else if (board[fromY][fromX] == king
7697         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7698                && toY == fromY && toX < fromX-1) {
7699         board[fromY][fromX] = EmptySquare;
7700         board[toY][toX] = king;
7701         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7702         board[fromY][BOARD_LEFT] = EmptySquare;
7703     } else if (board[fromY][fromX] == WhitePawn
7704                && toY >= BOARD_HEIGHT-promoRank
7705                && gameInfo.variant != VariantXiangqi
7706                ) {
7707         /* white pawn promotion */
7708         board[toY][toX] = CharToPiece(ToUpper(promoChar));
7709         if (board[toY][toX] == EmptySquare) {
7710             board[toY][toX] = WhiteQueen;
7711         }
7712         if(gameInfo.variant==VariantBughouse ||
7713            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7714             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7715         board[fromY][fromX] = EmptySquare;
7716     } else if ((fromY == BOARD_HEIGHT-4)
7717                && (toX != fromX)
7718                && gameInfo.variant != VariantXiangqi
7719                && gameInfo.variant != VariantBerolina
7720                && (board[fromY][fromX] == WhitePawn)
7721                && (board[toY][toX] == EmptySquare)) {
7722         board[fromY][fromX] = EmptySquare;
7723         board[toY][toX] = WhitePawn;
7724         captured = board[toY - 1][toX];
7725         board[toY - 1][toX] = EmptySquare;
7726     } else if ((fromY == BOARD_HEIGHT-4)
7727                && (toX == fromX)
7728                && gameInfo.variant == VariantBerolina
7729                && (board[fromY][fromX] == WhitePawn)
7730                && (board[toY][toX] == EmptySquare)) {
7731         board[fromY][fromX] = EmptySquare;
7732         board[toY][toX] = WhitePawn;
7733         if(oldEP & EP_BEROLIN_A) {
7734                 captured = board[fromY][fromX-1];
7735                 board[fromY][fromX-1] = EmptySquare;
7736         }else{  captured = board[fromY][fromX+1];
7737                 board[fromY][fromX+1] = EmptySquare;
7738         }
7739     } else if (board[fromY][fromX] == king
7740         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7741                && toY == fromY && toX > fromX+1) {
7742         board[fromY][fromX] = EmptySquare;
7743         board[toY][toX] = king;
7744         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7745         board[fromY][BOARD_RGHT-1] = EmptySquare;
7746     } else if (board[fromY][fromX] == king
7747         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7748                && toY == fromY && toX < fromX-1) {
7749         board[fromY][fromX] = EmptySquare;
7750         board[toY][toX] = king;
7751         board[toY][toX+1] = board[fromY][BOARD_LEFT];
7752         board[fromY][BOARD_LEFT] = EmptySquare;
7753     } else if (fromY == 7 && fromX == 3
7754                && board[fromY][fromX] == BlackKing
7755                && toY == 7 && toX == 5) {
7756         board[fromY][fromX] = EmptySquare;
7757         board[toY][toX] = BlackKing;
7758         board[fromY][7] = EmptySquare;
7759         board[toY][4] = BlackRook;
7760     } else if (fromY == 7 && fromX == 3
7761                && board[fromY][fromX] == BlackKing
7762                && toY == 7 && toX == 1) {
7763         board[fromY][fromX] = EmptySquare;
7764         board[toY][toX] = BlackKing;
7765         board[fromY][0] = EmptySquare;
7766         board[toY][2] = BlackRook;
7767     } else if (board[fromY][fromX] == BlackPawn
7768                && toY < promoRank
7769                && gameInfo.variant != VariantXiangqi
7770                ) {
7771         /* black pawn promotion */
7772         board[toY][toX] = CharToPiece(ToLower(promoChar));
7773         if (board[toY][toX] == EmptySquare) {
7774             board[toY][toX] = BlackQueen;
7775         }
7776         if(gameInfo.variant==VariantBughouse ||
7777            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7778             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7779         board[fromY][fromX] = EmptySquare;
7780     } else if ((fromY == 3)
7781                && (toX != fromX)
7782                && gameInfo.variant != VariantXiangqi
7783                && gameInfo.variant != VariantBerolina
7784                && (board[fromY][fromX] == BlackPawn)
7785                && (board[toY][toX] == EmptySquare)) {
7786         board[fromY][fromX] = EmptySquare;
7787         board[toY][toX] = BlackPawn;
7788         captured = board[toY + 1][toX];
7789         board[toY + 1][toX] = EmptySquare;
7790     } else if ((fromY == 3)
7791                && (toX == fromX)
7792                && gameInfo.variant == VariantBerolina
7793                && (board[fromY][fromX] == BlackPawn)
7794                && (board[toY][toX] == EmptySquare)) {
7795         board[fromY][fromX] = EmptySquare;
7796         board[toY][toX] = BlackPawn;
7797         if(oldEP & EP_BEROLIN_A) {
7798                 captured = board[fromY][fromX-1];
7799                 board[fromY][fromX-1] = EmptySquare;
7800         }else{  captured = board[fromY][fromX+1];
7801                 board[fromY][fromX+1] = EmptySquare;
7802         }
7803     } else {
7804         board[toY][toX] = board[fromY][fromX];
7805         board[fromY][fromX] = EmptySquare;
7806     }
7807
7808     /* [HGM] now we promote for Shogi, if needed */
7809     if(gameInfo.variant == VariantShogi && promoChar == 'q')
7810         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7811   }
7812
7813     if (gameInfo.holdingsWidth != 0) {
7814
7815       /* !!A lot more code needs to be written to support holdings  */
7816       /* [HGM] OK, so I have written it. Holdings are stored in the */
7817       /* penultimate board files, so they are automaticlly stored   */
7818       /* in the game history.                                       */
7819       if (fromY == DROP_RANK) {
7820         /* Delete from holdings, by decreasing count */
7821         /* and erasing image if necessary            */
7822         p = (int) fromX;
7823         if(p < (int) BlackPawn) { /* white drop */
7824              p -= (int)WhitePawn;
7825                  p = PieceToNumber((ChessSquare)p);
7826              if(p >= gameInfo.holdingsSize) p = 0;
7827              if(--board[p][BOARD_WIDTH-2] <= 0)
7828                   board[p][BOARD_WIDTH-1] = EmptySquare;
7829              if((int)board[p][BOARD_WIDTH-2] < 0)
7830                         board[p][BOARD_WIDTH-2] = 0;
7831         } else {                  /* black drop */
7832              p -= (int)BlackPawn;
7833                  p = PieceToNumber((ChessSquare)p);
7834              if(p >= gameInfo.holdingsSize) p = 0;
7835              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7836                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7837              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7838                         board[BOARD_HEIGHT-1-p][1] = 0;
7839         }
7840       }
7841       if (captured != EmptySquare && gameInfo.holdingsSize > 0
7842           && gameInfo.variant != VariantBughouse        ) {
7843         /* [HGM] holdings: Add to holdings, if holdings exist */
7844         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
7845                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7846                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7847         }
7848         p = (int) captured;
7849         if (p >= (int) BlackPawn) {
7850           p -= (int)BlackPawn;
7851           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7852                   /* in Shogi restore piece to its original  first */
7853                   captured = (ChessSquare) (DEMOTED captured);
7854                   p = DEMOTED p;
7855           }
7856           p = PieceToNumber((ChessSquare)p);
7857           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7858           board[p][BOARD_WIDTH-2]++;
7859           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7860         } else {
7861           p -= (int)WhitePawn;
7862           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7863                   captured = (ChessSquare) (DEMOTED captured);
7864                   p = DEMOTED p;
7865           }
7866           p = PieceToNumber((ChessSquare)p);
7867           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7868           board[BOARD_HEIGHT-1-p][1]++;
7869           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7870         }
7871       }
7872     } else if (gameInfo.variant == VariantAtomic) {
7873       if (captured != EmptySquare) {
7874         int y, x;
7875         for (y = toY-1; y <= toY+1; y++) {
7876           for (x = toX-1; x <= toX+1; x++) {
7877             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7878                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7879               board[y][x] = EmptySquare;
7880             }
7881           }
7882         }
7883         board[toY][toX] = EmptySquare;
7884       }
7885     }
7886     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7887         /* [HGM] Shogi promotions */
7888         board[toY][toX] = (ChessSquare) (PROMOTED piece);
7889     }
7890
7891     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
7892                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
7893         // [HGM] superchess: take promotion piece out of holdings
7894         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7895         if((int)piece < (int)BlackPawn) { // determine stm from piece color
7896             if(!--board[k][BOARD_WIDTH-2])
7897                 board[k][BOARD_WIDTH-1] = EmptySquare;
7898         } else {
7899             if(!--board[BOARD_HEIGHT-1-k][1])
7900                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7901         }
7902     }
7903
7904 }
7905
7906 /* Updates forwardMostMove */
7907 void
7908 MakeMove(fromX, fromY, toX, toY, promoChar)
7909      int fromX, fromY, toX, toY;
7910      int promoChar;
7911 {
7912 //    forwardMostMove++; // [HGM] bare: moved downstream
7913
7914     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7915         int timeLeft; static int lastLoadFlag=0; int king, piece;
7916         piece = boards[forwardMostMove][fromY][fromX];
7917         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7918         if(gameInfo.variant == VariantKnightmate)
7919             king += (int) WhiteUnicorn - (int) WhiteKing;
7920         if(forwardMostMove == 0) {
7921             if(blackPlaysFirst) 
7922                 fprintf(serverMoves, "%s;", second.tidy);
7923             fprintf(serverMoves, "%s;", first.tidy);
7924             if(!blackPlaysFirst) 
7925                 fprintf(serverMoves, "%s;", second.tidy);
7926         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7927         lastLoadFlag = loadFlag;
7928         // print base move
7929         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7930         // print castling suffix
7931         if( toY == fromY && piece == king ) {
7932             if(toX-fromX > 1)
7933                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7934             if(fromX-toX >1)
7935                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7936         }
7937         // e.p. suffix
7938         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7939              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
7940              boards[forwardMostMove][toY][toX] == EmptySquare
7941              && fromX != toX )
7942                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7943         // promotion suffix
7944         if(promoChar != NULLCHAR)
7945                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7946         if(!loadFlag) {
7947             fprintf(serverMoves, "/%d/%d",
7948                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7949             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7950             else                      timeLeft = blackTimeRemaining/1000;
7951             fprintf(serverMoves, "/%d", timeLeft);
7952         }
7953         fflush(serverMoves);
7954     }
7955
7956     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7957       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7958                         0, 1);
7959       return;
7960     }
7961     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7962     if (commentList[forwardMostMove+1] != NULL) {
7963         free(commentList[forwardMostMove+1]);
7964         commentList[forwardMostMove+1] = NULL;
7965     }
7966     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7967     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7968     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7969     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7970     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7971     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7972     gameInfo.result = GameUnfinished;
7973     if (gameInfo.resultDetails != NULL) {
7974         free(gameInfo.resultDetails);
7975         gameInfo.resultDetails = NULL;
7976     }
7977     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7978                               moveList[forwardMostMove - 1]);
7979     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7980                              PosFlags(forwardMostMove - 1),
7981                              fromY, fromX, toY, toX, promoChar,
7982                              parseList[forwardMostMove - 1]);
7983     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7984       case MT_NONE:
7985       case MT_STALEMATE:
7986       default:
7987         break;
7988       case MT_CHECK:
7989         if(gameInfo.variant != VariantShogi)
7990             strcat(parseList[forwardMostMove - 1], "+");
7991         break;
7992       case MT_CHECKMATE:
7993       case MT_STAINMATE:
7994         strcat(parseList[forwardMostMove - 1], "#");
7995         break;
7996     }
7997     if (appData.debugMode) {
7998         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7999     }
8000
8001 }
8002
8003 /* Updates currentMove if not pausing */
8004 void
8005 ShowMove(fromX, fromY, toX, toY)
8006 {
8007     int instant = (gameMode == PlayFromGameFile) ?
8008         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8009     if(appData.noGUI) return;
8010     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8011         if (!instant) {
8012             if (forwardMostMove == currentMove + 1) {
8013                 AnimateMove(boards[forwardMostMove - 1],
8014                             fromX, fromY, toX, toY);
8015             }
8016             if (appData.highlightLastMove) {
8017                 SetHighlights(fromX, fromY, toX, toY);
8018             }
8019         }
8020         currentMove = forwardMostMove;
8021     }
8022
8023     if (instant) return;
8024
8025     DisplayMove(currentMove - 1);
8026     DrawPosition(FALSE, boards[currentMove]);
8027     DisplayBothClocks();
8028     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8029 }
8030
8031 void SendEgtPath(ChessProgramState *cps)
8032 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8033         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8034
8035         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8036
8037         while(*p) {
8038             char c, *q = name+1, *r, *s;
8039
8040             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8041             while(*p && *p != ',') *q++ = *p++;
8042             *q++ = ':'; *q = 0;
8043             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8044                 strcmp(name, ",nalimov:") == 0 ) {
8045                 // take nalimov path from the menu-changeable option first, if it is defined
8046                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8047                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8048             } else
8049             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8050                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8051                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8052                 s = r = StrStr(s, ":") + 1; // beginning of path info
8053                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8054                 c = *r; *r = 0;             // temporarily null-terminate path info
8055                     *--q = 0;               // strip of trailig ':' from name
8056                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8057                 *r = c;
8058                 SendToProgram(buf,cps);     // send egtbpath command for this format
8059             }
8060             if(*p == ',') p++; // read away comma to position for next format name
8061         }
8062 }
8063
8064 void
8065 InitChessProgram(cps, setup)
8066      ChessProgramState *cps;
8067      int setup; /* [HGM] needed to setup FRC opening position */
8068 {
8069     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8070     if (appData.noChessProgram) return;
8071     hintRequested = FALSE;
8072     bookRequested = FALSE;
8073
8074     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8075     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8076     if(cps->memSize) { /* [HGM] memory */
8077         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8078         SendToProgram(buf, cps);
8079     }
8080     SendEgtPath(cps); /* [HGM] EGT */
8081     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8082         sprintf(buf, "cores %d\n", appData.smpCores);
8083         SendToProgram(buf, cps);
8084     }
8085
8086     SendToProgram(cps->initString, cps);
8087     if (gameInfo.variant != VariantNormal &&
8088         gameInfo.variant != VariantLoadable
8089         /* [HGM] also send variant if board size non-standard */
8090         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8091                                             ) {
8092       char *v = VariantName(gameInfo.variant);
8093       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8094         /* [HGM] in protocol 1 we have to assume all variants valid */
8095         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8096         DisplayFatalError(buf, 0, 1);
8097         return;
8098       }
8099
8100       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8101       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8102       if( gameInfo.variant == VariantXiangqi )
8103            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8104       if( gameInfo.variant == VariantShogi )
8105            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8106       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8107            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8108       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8109                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8110            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8111       if( gameInfo.variant == VariantCourier )
8112            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8113       if( gameInfo.variant == VariantSuper )
8114            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8115       if( gameInfo.variant == VariantGreat )
8116            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8117
8118       if(overruled) {
8119            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8120                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8121            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8122            if(StrStr(cps->variants, b) == NULL) { 
8123                // specific sized variant not known, check if general sizing allowed
8124                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8125                    if(StrStr(cps->variants, "boardsize") == NULL) {
8126                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8127                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8128                        DisplayFatalError(buf, 0, 1);
8129                        return;
8130                    }
8131                    /* [HGM] here we really should compare with the maximum supported board size */
8132                }
8133            }
8134       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8135       sprintf(buf, "variant %s\n", b);
8136       SendToProgram(buf, cps);
8137     }
8138     currentlyInitializedVariant = gameInfo.variant;
8139
8140     /* [HGM] send opening position in FRC to first engine */
8141     if(setup) {
8142           SendToProgram("force\n", cps);
8143           SendBoard(cps, 0);
8144           /* engine is now in force mode! Set flag to wake it up after first move. */
8145           setboardSpoiledMachineBlack = 1;
8146     }
8147
8148     if (cps->sendICS) {
8149       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8150       SendToProgram(buf, cps);
8151     }
8152     cps->maybeThinking = FALSE;
8153     cps->offeredDraw = 0;
8154     if (!appData.icsActive) {
8155         SendTimeControl(cps, movesPerSession, timeControl,
8156                         timeIncrement, appData.searchDepth,
8157                         searchTime);
8158     }
8159     if (appData.showThinking 
8160         // [HGM] thinking: four options require thinking output to be sent
8161         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8162                                 ) {
8163         SendToProgram("post\n", cps);
8164     }
8165     SendToProgram("hard\n", cps);
8166     if (!appData.ponderNextMove) {
8167         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8168            it without being sure what state we are in first.  "hard"
8169            is not a toggle, so that one is OK.
8170          */
8171         SendToProgram("easy\n", cps);
8172     }
8173     if (cps->usePing) {
8174       sprintf(buf, "ping %d\n", ++cps->lastPing);
8175       SendToProgram(buf, cps);
8176     }
8177     cps->initDone = TRUE;
8178 }   
8179
8180
8181 void
8182 StartChessProgram(cps)
8183      ChessProgramState *cps;
8184 {
8185     char buf[MSG_SIZ];
8186     int err;
8187
8188     if (appData.noChessProgram) return;
8189     cps->initDone = FALSE;
8190
8191     if (strcmp(cps->host, "localhost") == 0) {
8192         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8193     } else if (*appData.remoteShell == NULLCHAR) {
8194         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8195     } else {
8196         if (*appData.remoteUser == NULLCHAR) {
8197           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8198                     cps->program);
8199         } else {
8200           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8201                     cps->host, appData.remoteUser, cps->program);
8202         }
8203         err = StartChildProcess(buf, "", &cps->pr);
8204     }
8205     
8206     if (err != 0) {
8207         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8208         DisplayFatalError(buf, err, 1);
8209         cps->pr = NoProc;
8210         cps->isr = NULL;
8211         return;
8212     }
8213     
8214     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8215     if (cps->protocolVersion > 1) {
8216       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8217       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8218       cps->comboCnt = 0;  //                and values of combo boxes
8219       SendToProgram(buf, cps);
8220     } else {
8221       SendToProgram("xboard\n", cps);
8222     }
8223 }
8224
8225
8226 void
8227 TwoMachinesEventIfReady P((void))
8228 {
8229   if (first.lastPing != first.lastPong) {
8230     DisplayMessage("", _("Waiting for first chess program"));
8231     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8232     return;
8233   }
8234   if (second.lastPing != second.lastPong) {
8235     DisplayMessage("", _("Waiting for second chess program"));
8236     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8237     return;
8238   }
8239   ThawUI();
8240   TwoMachinesEvent();
8241 }
8242
8243 void
8244 NextMatchGame P((void))
8245 {
8246     int index; /* [HGM] autoinc: step load index during match */
8247     Reset(FALSE, TRUE);
8248     if (*appData.loadGameFile != NULLCHAR) {
8249         index = appData.loadGameIndex;
8250         if(index < 0) { // [HGM] autoinc
8251             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8252             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8253         } 
8254         LoadGameFromFile(appData.loadGameFile,
8255                          index,
8256                          appData.loadGameFile, FALSE);
8257     } else if (*appData.loadPositionFile != NULLCHAR) {
8258         index = appData.loadPositionIndex;
8259         if(index < 0) { // [HGM] autoinc
8260             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8261             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8262         } 
8263         LoadPositionFromFile(appData.loadPositionFile,
8264                              index,
8265                              appData.loadPositionFile);
8266     }
8267     TwoMachinesEventIfReady();
8268 }
8269
8270 void UserAdjudicationEvent( int result )
8271 {
8272     ChessMove gameResult = GameIsDrawn;
8273
8274     if( result > 0 ) {
8275         gameResult = WhiteWins;
8276     }
8277     else if( result < 0 ) {
8278         gameResult = BlackWins;
8279     }
8280
8281     if( gameMode == TwoMachinesPlay ) {
8282         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8283     }
8284 }
8285
8286
8287 // [HGM] save: calculate checksum of game to make games easily identifiable
8288 int StringCheckSum(char *s)
8289 {
8290         int i = 0;
8291         if(s==NULL) return 0;
8292         while(*s) i = i*259 + *s++;
8293         return i;
8294 }
8295
8296 int GameCheckSum()
8297 {
8298         int i, sum=0;
8299         for(i=backwardMostMove; i<forwardMostMove; i++) {
8300                 sum += pvInfoList[i].depth;
8301                 sum += StringCheckSum(parseList[i]);
8302                 sum += StringCheckSum(commentList[i]);
8303                 sum *= 261;
8304         }
8305         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8306         return sum + StringCheckSum(commentList[i]);
8307 } // end of save patch
8308
8309 void
8310 GameEnds(result, resultDetails, whosays)
8311      ChessMove result;
8312      char *resultDetails;
8313      int whosays;
8314 {
8315     GameMode nextGameMode;
8316     int isIcsGame;
8317     char buf[MSG_SIZ];
8318
8319     if(endingGame) return; /* [HGM] crash: forbid recursion */
8320     endingGame = 1;
8321
8322     if (appData.debugMode) {
8323       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8324               result, resultDetails ? resultDetails : "(null)", whosays);
8325     }
8326
8327     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8328         /* If we are playing on ICS, the server decides when the
8329            game is over, but the engine can offer to draw, claim 
8330            a draw, or resign. 
8331          */
8332 #if ZIPPY
8333         if (appData.zippyPlay && first.initDone) {
8334             if (result == GameIsDrawn) {
8335                 /* In case draw still needs to be claimed */
8336                 SendToICS(ics_prefix);
8337                 SendToICS("draw\n");
8338             } else if (StrCaseStr(resultDetails, "resign")) {
8339                 SendToICS(ics_prefix);
8340                 SendToICS("resign\n");
8341             }
8342         }
8343 #endif
8344         endingGame = 0; /* [HGM] crash */
8345         return;
8346     }
8347
8348     /* If we're loading the game from a file, stop */
8349     if (whosays == GE_FILE) {
8350       (void) StopLoadGameTimer();
8351       gameFileFP = NULL;
8352     }
8353
8354     /* Cancel draw offers */
8355     first.offeredDraw = second.offeredDraw = 0;
8356
8357     /* If this is an ICS game, only ICS can really say it's done;
8358        if not, anyone can. */
8359     isIcsGame = (gameMode == IcsPlayingWhite || 
8360                  gameMode == IcsPlayingBlack || 
8361                  gameMode == IcsObserving    || 
8362                  gameMode == IcsExamining);
8363
8364     if (!isIcsGame || whosays == GE_ICS) {
8365         /* OK -- not an ICS game, or ICS said it was done */
8366         StopClocks();
8367         if (!isIcsGame && !appData.noChessProgram) 
8368           SetUserThinkingEnables();
8369     
8370         /* [HGM] if a machine claims the game end we verify this claim */
8371         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8372             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8373                 char claimer;
8374                 ChessMove trueResult = (ChessMove) -1;
8375
8376                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8377                                             first.twoMachinesColor[0] :
8378                                             second.twoMachinesColor[0] ;
8379
8380                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8381                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8382                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8383                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8384                 } else
8385                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8386                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8387                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8388                 } else
8389                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8390                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8391                 }
8392
8393                 // now verify win claims, but not in drop games, as we don't understand those yet
8394                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8395                                                  || gameInfo.variant == VariantGreat) &&
8396                     (result == WhiteWins && claimer == 'w' ||
8397                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8398                       if (appData.debugMode) {
8399                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8400                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8401                       }
8402                       if(result != trueResult) {
8403                               sprintf(buf, "False win claim: '%s'", resultDetails);
8404                               result = claimer == 'w' ? BlackWins : WhiteWins;
8405                               resultDetails = buf;
8406                       }
8407                 } else
8408                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8409                     && (forwardMostMove <= backwardMostMove ||
8410                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8411                         (claimer=='b')==(forwardMostMove&1))
8412                                                                                   ) {
8413                       /* [HGM] verify: draws that were not flagged are false claims */
8414                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8415                       result = claimer == 'w' ? BlackWins : WhiteWins;
8416                       resultDetails = buf;
8417                 }
8418                 /* (Claiming a loss is accepted no questions asked!) */
8419             }
8420             /* [HGM] bare: don't allow bare King to win */
8421             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8422                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8423                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8424                && result != GameIsDrawn)
8425             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8426                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8427                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8428                         if(p >= 0 && p <= (int)WhiteKing) k++;
8429                 }
8430                 if (appData.debugMode) {
8431                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8432                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8433                 }
8434                 if(k <= 1) {
8435                         result = GameIsDrawn;
8436                         sprintf(buf, "%s but bare king", resultDetails);
8437                         resultDetails = buf;
8438                 }
8439             }
8440         }
8441
8442
8443         if(serverMoves != NULL && !loadFlag) { char c = '=';
8444             if(result==WhiteWins) c = '+';
8445             if(result==BlackWins) c = '-';
8446             if(resultDetails != NULL)
8447                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8448         }
8449         if (resultDetails != NULL) {
8450             gameInfo.result = result;
8451             gameInfo.resultDetails = StrSave(resultDetails);
8452
8453             /* display last move only if game was not loaded from file */
8454             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8455                 DisplayMove(currentMove - 1);
8456     
8457             if (forwardMostMove != 0) {
8458                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8459                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8460                                                                 ) {
8461                     if (*appData.saveGameFile != NULLCHAR) {
8462                         SaveGameToFile(appData.saveGameFile, TRUE);
8463                     } else if (appData.autoSaveGames) {
8464                         AutoSaveGame();
8465                     }
8466                     if (*appData.savePositionFile != NULLCHAR) {
8467                         SavePositionToFile(appData.savePositionFile);
8468                     }
8469                 }
8470             }
8471
8472             /* Tell program how game ended in case it is learning */
8473             /* [HGM] Moved this to after saving the PGN, just in case */
8474             /* engine died and we got here through time loss. In that */
8475             /* case we will get a fatal error writing the pipe, which */
8476             /* would otherwise lose us the PGN.                       */
8477             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8478             /* output during GameEnds should never be fatal anymore   */
8479             if (gameMode == MachinePlaysWhite ||
8480                 gameMode == MachinePlaysBlack ||
8481                 gameMode == TwoMachinesPlay ||
8482                 gameMode == IcsPlayingWhite ||
8483                 gameMode == IcsPlayingBlack ||
8484                 gameMode == BeginningOfGame) {
8485                 char buf[MSG_SIZ];
8486                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8487                         resultDetails);
8488                 if (first.pr != NoProc) {
8489                     SendToProgram(buf, &first);
8490                 }
8491                 if (second.pr != NoProc &&
8492                     gameMode == TwoMachinesPlay) {
8493                     SendToProgram(buf, &second);
8494                 }
8495             }
8496         }
8497
8498         if (appData.icsActive) {
8499             if (appData.quietPlay &&
8500                 (gameMode == IcsPlayingWhite ||
8501                  gameMode == IcsPlayingBlack)) {
8502                 SendToICS(ics_prefix);
8503                 SendToICS("set shout 1\n");
8504             }
8505             nextGameMode = IcsIdle;
8506             ics_user_moved = FALSE;
8507             /* clean up premove.  It's ugly when the game has ended and the
8508              * premove highlights are still on the board.
8509              */
8510             if (gotPremove) {
8511               gotPremove = FALSE;
8512               ClearPremoveHighlights();
8513               DrawPosition(FALSE, boards[currentMove]);
8514             }
8515             if (whosays == GE_ICS) {
8516                 switch (result) {
8517                 case WhiteWins:
8518                     if (gameMode == IcsPlayingWhite)
8519                         PlayIcsWinSound();
8520                     else if(gameMode == IcsPlayingBlack)
8521                         PlayIcsLossSound();
8522                     break;
8523                 case BlackWins:
8524                     if (gameMode == IcsPlayingBlack)
8525                         PlayIcsWinSound();
8526                     else if(gameMode == IcsPlayingWhite)
8527                         PlayIcsLossSound();
8528                     break;
8529                 case GameIsDrawn:
8530                     PlayIcsDrawSound();
8531                     break;
8532                 default:
8533                     PlayIcsUnfinishedSound();
8534                 }
8535             }
8536         } else if (gameMode == EditGame ||
8537                    gameMode == PlayFromGameFile || 
8538                    gameMode == AnalyzeMode || 
8539                    gameMode == AnalyzeFile) {
8540             nextGameMode = gameMode;
8541         } else {
8542             nextGameMode = EndOfGame;
8543         }
8544         pausing = FALSE;
8545         ModeHighlight();
8546     } else {
8547         nextGameMode = gameMode;
8548     }
8549
8550     if (appData.noChessProgram) {
8551         gameMode = nextGameMode;
8552         ModeHighlight();
8553         endingGame = 0; /* [HGM] crash */
8554         return;
8555     }
8556
8557     if (first.reuse) {
8558         /* Put first chess program into idle state */
8559         if (first.pr != NoProc &&
8560             (gameMode == MachinePlaysWhite ||
8561              gameMode == MachinePlaysBlack ||
8562              gameMode == TwoMachinesPlay ||
8563              gameMode == IcsPlayingWhite ||
8564              gameMode == IcsPlayingBlack ||
8565              gameMode == BeginningOfGame)) {
8566             SendToProgram("force\n", &first);
8567             if (first.usePing) {
8568               char buf[MSG_SIZ];
8569               sprintf(buf, "ping %d\n", ++first.lastPing);
8570               SendToProgram(buf, &first);
8571             }
8572         }
8573     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8574         /* Kill off first chess program */
8575         if (first.isr != NULL)
8576           RemoveInputSource(first.isr);
8577         first.isr = NULL;
8578     
8579         if (first.pr != NoProc) {
8580             ExitAnalyzeMode();
8581             DoSleep( appData.delayBeforeQuit );
8582             SendToProgram("quit\n", &first);
8583             DoSleep( appData.delayAfterQuit );
8584             DestroyChildProcess(first.pr, first.useSigterm);
8585         }
8586         first.pr = NoProc;
8587     }
8588     if (second.reuse) {
8589         /* Put second chess program into idle state */
8590         if (second.pr != NoProc &&
8591             gameMode == TwoMachinesPlay) {
8592             SendToProgram("force\n", &second);
8593             if (second.usePing) {
8594               char buf[MSG_SIZ];
8595               sprintf(buf, "ping %d\n", ++second.lastPing);
8596               SendToProgram(buf, &second);
8597             }
8598         }
8599     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8600         /* Kill off second chess program */
8601         if (second.isr != NULL)
8602           RemoveInputSource(second.isr);
8603         second.isr = NULL;
8604     
8605         if (second.pr != NoProc) {
8606             DoSleep( appData.delayBeforeQuit );
8607             SendToProgram("quit\n", &second);
8608             DoSleep( appData.delayAfterQuit );
8609             DestroyChildProcess(second.pr, second.useSigterm);
8610         }
8611         second.pr = NoProc;
8612     }
8613
8614     if (matchMode && gameMode == TwoMachinesPlay) {
8615         switch (result) {
8616         case WhiteWins:
8617           if (first.twoMachinesColor[0] == 'w') {
8618             first.matchWins++;
8619           } else {
8620             second.matchWins++;
8621           }
8622           break;
8623         case BlackWins:
8624           if (first.twoMachinesColor[0] == 'b') {
8625             first.matchWins++;
8626           } else {
8627             second.matchWins++;
8628           }
8629           break;
8630         default:
8631           break;
8632         }
8633         if (matchGame < appData.matchGames) {
8634             char *tmp;
8635             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8636                 tmp = first.twoMachinesColor;
8637                 first.twoMachinesColor = second.twoMachinesColor;
8638                 second.twoMachinesColor = tmp;
8639             }
8640             gameMode = nextGameMode;
8641             matchGame++;
8642             if(appData.matchPause>10000 || appData.matchPause<10)
8643                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8644             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8645             endingGame = 0; /* [HGM] crash */
8646             return;
8647         } else {
8648             char buf[MSG_SIZ];
8649             gameMode = nextGameMode;
8650             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8651                     first.tidy, second.tidy,
8652                     first.matchWins, second.matchWins,
8653                     appData.matchGames - (first.matchWins + second.matchWins));
8654             DisplayFatalError(buf, 0, 0);
8655         }
8656     }
8657     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8658         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8659       ExitAnalyzeMode();
8660     gameMode = nextGameMode;
8661     ModeHighlight();
8662     endingGame = 0;  /* [HGM] crash */
8663 }
8664
8665 /* Assumes program was just initialized (initString sent).
8666    Leaves program in force mode. */
8667 void
8668 FeedMovesToProgram(cps, upto) 
8669      ChessProgramState *cps;
8670      int upto;
8671 {
8672     int i;
8673     
8674     if (appData.debugMode)
8675       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8676               startedFromSetupPosition ? "position and " : "",
8677               backwardMostMove, upto, cps->which);
8678     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8679         // [HGM] variantswitch: make engine aware of new variant
8680         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8681                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8682         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8683         SendToProgram(buf, cps);
8684         currentlyInitializedVariant = gameInfo.variant;
8685     }
8686     SendToProgram("force\n", cps);
8687     if (startedFromSetupPosition) {
8688         SendBoard(cps, backwardMostMove);
8689     if (appData.debugMode) {
8690         fprintf(debugFP, "feedMoves\n");
8691     }
8692     }
8693     for (i = backwardMostMove; i < upto; i++) {
8694         SendMoveToProgram(i, cps);
8695     }
8696 }
8697
8698
8699 void
8700 ResurrectChessProgram()
8701 {
8702      /* The chess program may have exited.
8703         If so, restart it and feed it all the moves made so far. */
8704
8705     if (appData.noChessProgram || first.pr != NoProc) return;
8706     
8707     StartChessProgram(&first);
8708     InitChessProgram(&first, FALSE);
8709     FeedMovesToProgram(&first, currentMove);
8710
8711     if (!first.sendTime) {
8712         /* can't tell gnuchess what its clock should read,
8713            so we bow to its notion. */
8714         ResetClocks();
8715         timeRemaining[0][currentMove] = whiteTimeRemaining;
8716         timeRemaining[1][currentMove] = blackTimeRemaining;
8717     }
8718
8719     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8720                 appData.icsEngineAnalyze) && first.analysisSupport) {
8721       SendToProgram("analyze\n", &first);
8722       first.analyzing = TRUE;
8723     }
8724 }
8725
8726 /*
8727  * Button procedures
8728  */
8729 void
8730 Reset(redraw, init)
8731      int redraw, init;
8732 {
8733     int i;
8734
8735     if (appData.debugMode) {
8736         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8737                 redraw, init, gameMode);
8738     }
8739     CleanupTail(); // [HGM] vari: delete any stored variations
8740     pausing = pauseExamInvalid = FALSE;
8741     startedFromSetupPosition = blackPlaysFirst = FALSE;
8742     firstMove = TRUE;
8743     whiteFlag = blackFlag = FALSE;
8744     userOfferedDraw = FALSE;
8745     hintRequested = bookRequested = FALSE;
8746     first.maybeThinking = FALSE;
8747     second.maybeThinking = FALSE;
8748     first.bookSuspend = FALSE; // [HGM] book
8749     second.bookSuspend = FALSE;
8750     thinkOutput[0] = NULLCHAR;
8751     lastHint[0] = NULLCHAR;
8752     ClearGameInfo(&gameInfo);
8753     gameInfo.variant = StringToVariant(appData.variant);
8754     ics_user_moved = ics_clock_paused = FALSE;
8755     ics_getting_history = H_FALSE;
8756     ics_gamenum = -1;
8757     white_holding[0] = black_holding[0] = NULLCHAR;
8758     ClearProgramStats();
8759     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8760     
8761     ResetFrontEnd();
8762     ClearHighlights();
8763     flipView = appData.flipView;
8764     ClearPremoveHighlights();
8765     gotPremove = FALSE;
8766     alarmSounded = FALSE;
8767
8768     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8769     if(appData.serverMovesName != NULL) {
8770         /* [HGM] prepare to make moves file for broadcasting */
8771         clock_t t = clock();
8772         if(serverMoves != NULL) fclose(serverMoves);
8773         serverMoves = fopen(appData.serverMovesName, "r");
8774         if(serverMoves != NULL) {
8775             fclose(serverMoves);
8776             /* delay 15 sec before overwriting, so all clients can see end */
8777             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8778         }
8779         serverMoves = fopen(appData.serverMovesName, "w");
8780     }
8781
8782     ExitAnalyzeMode();
8783     gameMode = BeginningOfGame;
8784     ModeHighlight();
8785     if(appData.icsActive) gameInfo.variant = VariantNormal;
8786     currentMove = forwardMostMove = backwardMostMove = 0;
8787     InitPosition(redraw);
8788     for (i = 0; i < MAX_MOVES; i++) {
8789         if (commentList[i] != NULL) {
8790             free(commentList[i]);
8791             commentList[i] = NULL;
8792         }
8793     }
8794     ResetClocks();
8795     timeRemaining[0][0] = whiteTimeRemaining;
8796     timeRemaining[1][0] = blackTimeRemaining;
8797     if (first.pr == NULL) {
8798         StartChessProgram(&first);
8799     }
8800     if (init) {
8801             InitChessProgram(&first, startedFromSetupPosition);
8802     }
8803     DisplayTitle("");
8804     DisplayMessage("", "");
8805     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8806     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8807 }
8808
8809 void
8810 AutoPlayGameLoop()
8811 {
8812     for (;;) {
8813         if (!AutoPlayOneMove())
8814           return;
8815         if (matchMode || appData.timeDelay == 0)
8816           continue;
8817         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8818           return;
8819         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8820         break;
8821     }
8822 }
8823
8824
8825 int
8826 AutoPlayOneMove()
8827 {
8828     int fromX, fromY, toX, toY;
8829
8830     if (appData.debugMode) {
8831       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8832     }
8833
8834     if (gameMode != PlayFromGameFile)
8835       return FALSE;
8836
8837     if (currentMove >= forwardMostMove) {
8838       gameMode = EditGame;
8839       ModeHighlight();
8840
8841       /* [AS] Clear current move marker at the end of a game */
8842       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8843
8844       return FALSE;
8845     }
8846     
8847     toX = moveList[currentMove][2] - AAA;
8848     toY = moveList[currentMove][3] - ONE;
8849
8850     if (moveList[currentMove][1] == '@') {
8851         if (appData.highlightLastMove) {
8852             SetHighlights(-1, -1, toX, toY);
8853         }
8854     } else {
8855         fromX = moveList[currentMove][0] - AAA;
8856         fromY = moveList[currentMove][1] - ONE;
8857
8858         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8859
8860         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8861
8862         if (appData.highlightLastMove) {
8863             SetHighlights(fromX, fromY, toX, toY);
8864         }
8865     }
8866     DisplayMove(currentMove);
8867     SendMoveToProgram(currentMove++, &first);
8868     DisplayBothClocks();
8869     DrawPosition(FALSE, boards[currentMove]);
8870     // [HGM] PV info: always display, routine tests if empty
8871     DisplayComment(currentMove - 1, commentList[currentMove]);
8872     return TRUE;
8873 }
8874
8875
8876 int
8877 LoadGameOneMove(readAhead)
8878      ChessMove readAhead;
8879 {
8880     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8881     char promoChar = NULLCHAR;
8882     ChessMove moveType;
8883     char move[MSG_SIZ];
8884     char *p, *q;
8885     
8886     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
8887         gameMode != AnalyzeMode && gameMode != Training) {
8888         gameFileFP = NULL;
8889         return FALSE;
8890     }
8891     
8892     yyboardindex = forwardMostMove;
8893     if (readAhead != (ChessMove)0) {
8894       moveType = readAhead;
8895     } else {
8896       if (gameFileFP == NULL)
8897           return FALSE;
8898       moveType = (ChessMove) yylex();
8899     }
8900     
8901     done = FALSE;
8902     switch (moveType) {
8903       case Comment:
8904         if (appData.debugMode) 
8905           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8906         p = yy_text;
8907
8908         /* append the comment but don't display it */
8909         AppendComment(currentMove, p, FALSE);
8910         return TRUE;
8911
8912       case WhiteCapturesEnPassant:
8913       case BlackCapturesEnPassant:
8914       case WhitePromotionChancellor:
8915       case BlackPromotionChancellor:
8916       case WhitePromotionArchbishop:
8917       case BlackPromotionArchbishop:
8918       case WhitePromotionCentaur:
8919       case BlackPromotionCentaur:
8920       case WhitePromotionQueen:
8921       case BlackPromotionQueen:
8922       case WhitePromotionRook:
8923       case BlackPromotionRook:
8924       case WhitePromotionBishop:
8925       case BlackPromotionBishop:
8926       case WhitePromotionKnight:
8927       case BlackPromotionKnight:
8928       case WhitePromotionKing:
8929       case BlackPromotionKing:
8930       case NormalMove:
8931       case WhiteKingSideCastle:
8932       case WhiteQueenSideCastle:
8933       case BlackKingSideCastle:
8934       case BlackQueenSideCastle:
8935       case WhiteKingSideCastleWild:
8936       case WhiteQueenSideCastleWild:
8937       case BlackKingSideCastleWild:
8938       case BlackQueenSideCastleWild:
8939       /* PUSH Fabien */
8940       case WhiteHSideCastleFR:
8941       case WhiteASideCastleFR:
8942       case BlackHSideCastleFR:
8943       case BlackASideCastleFR:
8944       /* POP Fabien */
8945         if (appData.debugMode)
8946           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8947         fromX = currentMoveString[0] - AAA;
8948         fromY = currentMoveString[1] - ONE;
8949         toX = currentMoveString[2] - AAA;
8950         toY = currentMoveString[3] - ONE;
8951         promoChar = currentMoveString[4];
8952         break;
8953
8954       case WhiteDrop:
8955       case BlackDrop:
8956         if (appData.debugMode)
8957           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8958         fromX = moveType == WhiteDrop ?
8959           (int) CharToPiece(ToUpper(currentMoveString[0])) :
8960         (int) CharToPiece(ToLower(currentMoveString[0]));
8961         fromY = DROP_RANK;
8962         toX = currentMoveString[2] - AAA;
8963         toY = currentMoveString[3] - ONE;
8964         break;
8965
8966       case WhiteWins:
8967       case BlackWins:
8968       case GameIsDrawn:
8969       case GameUnfinished:
8970         if (appData.debugMode)
8971           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8972         p = strchr(yy_text, '{');
8973         if (p == NULL) p = strchr(yy_text, '(');
8974         if (p == NULL) {
8975             p = yy_text;
8976             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8977         } else {
8978             q = strchr(p, *p == '{' ? '}' : ')');
8979             if (q != NULL) *q = NULLCHAR;
8980             p++;
8981         }
8982         GameEnds(moveType, p, GE_FILE);
8983         done = TRUE;
8984         if (cmailMsgLoaded) {
8985             ClearHighlights();
8986             flipView = WhiteOnMove(currentMove);
8987             if (moveType == GameUnfinished) flipView = !flipView;
8988             if (appData.debugMode)
8989               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8990         }
8991         break;
8992
8993       case (ChessMove) 0:       /* end of file */
8994         if (appData.debugMode)
8995           fprintf(debugFP, "Parser hit end of file\n");
8996         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8997           case MT_NONE:
8998           case MT_CHECK:
8999             break;
9000           case MT_CHECKMATE:
9001           case MT_STAINMATE:
9002             if (WhiteOnMove(currentMove)) {
9003                 GameEnds(BlackWins, "Black mates", GE_FILE);
9004             } else {
9005                 GameEnds(WhiteWins, "White mates", GE_FILE);
9006             }
9007             break;
9008           case MT_STALEMATE:
9009             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9010             break;
9011         }
9012         done = TRUE;
9013         break;
9014
9015       case MoveNumberOne:
9016         if (lastLoadGameStart == GNUChessGame) {
9017             /* GNUChessGames have numbers, but they aren't move numbers */
9018             if (appData.debugMode)
9019               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9020                       yy_text, (int) moveType);
9021             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9022         }
9023         /* else fall thru */
9024
9025       case XBoardGame:
9026       case GNUChessGame:
9027       case PGNTag:
9028         /* Reached start of next game in file */
9029         if (appData.debugMode)
9030           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9031         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9032           case MT_NONE:
9033           case MT_CHECK:
9034             break;
9035           case MT_CHECKMATE:
9036           case MT_STAINMATE:
9037             if (WhiteOnMove(currentMove)) {
9038                 GameEnds(BlackWins, "Black mates", GE_FILE);
9039             } else {
9040                 GameEnds(WhiteWins, "White mates", GE_FILE);
9041             }
9042             break;
9043           case MT_STALEMATE:
9044             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9045             break;
9046         }
9047         done = TRUE;
9048         break;
9049
9050       case PositionDiagram:     /* should not happen; ignore */
9051       case ElapsedTime:         /* ignore */
9052       case NAG:                 /* ignore */
9053         if (appData.debugMode)
9054           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9055                   yy_text, (int) moveType);
9056         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9057
9058       case IllegalMove:
9059         if (appData.testLegality) {
9060             if (appData.debugMode)
9061               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9062             sprintf(move, _("Illegal move: %d.%s%s"),
9063                     (forwardMostMove / 2) + 1,
9064                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9065             DisplayError(move, 0);
9066             done = TRUE;
9067         } else {
9068             if (appData.debugMode)
9069               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9070                       yy_text, currentMoveString);
9071             fromX = currentMoveString[0] - AAA;
9072             fromY = currentMoveString[1] - ONE;
9073             toX = currentMoveString[2] - AAA;
9074             toY = currentMoveString[3] - ONE;
9075             promoChar = currentMoveString[4];
9076         }
9077         break;
9078
9079       case AmbiguousMove:
9080         if (appData.debugMode)
9081           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9082         sprintf(move, _("Ambiguous move: %d.%s%s"),
9083                 (forwardMostMove / 2) + 1,
9084                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9085         DisplayError(move, 0);
9086         done = TRUE;
9087         break;
9088
9089       default:
9090       case ImpossibleMove:
9091         if (appData.debugMode)
9092           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9093         sprintf(move, _("Illegal move: %d.%s%s"),
9094                 (forwardMostMove / 2) + 1,
9095                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9096         DisplayError(move, 0);
9097         done = TRUE;
9098         break;
9099     }
9100
9101     if (done) {
9102         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9103             DrawPosition(FALSE, boards[currentMove]);
9104             DisplayBothClocks();
9105             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9106               DisplayComment(currentMove - 1, commentList[currentMove]);
9107         }
9108         (void) StopLoadGameTimer();
9109         gameFileFP = NULL;
9110         cmailOldMove = forwardMostMove;
9111         return FALSE;
9112     } else {
9113         /* currentMoveString is set as a side-effect of yylex */
9114         strcat(currentMoveString, "\n");
9115         strcpy(moveList[forwardMostMove], currentMoveString);
9116         
9117         thinkOutput[0] = NULLCHAR;
9118         MakeMove(fromX, fromY, toX, toY, promoChar);
9119         currentMove = forwardMostMove;
9120         return TRUE;
9121     }
9122 }
9123
9124 /* Load the nth game from the given file */
9125 int
9126 LoadGameFromFile(filename, n, title, useList)
9127      char *filename;
9128      int n;
9129      char *title;
9130      /*Boolean*/ int useList;
9131 {
9132     FILE *f;
9133     char buf[MSG_SIZ];
9134
9135     if (strcmp(filename, "-") == 0) {
9136         f = stdin;
9137         title = "stdin";
9138     } else {
9139         f = fopen(filename, "rb");
9140         if (f == NULL) {
9141           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9142             DisplayError(buf, errno);
9143             return FALSE;
9144         }
9145     }
9146     if (fseek(f, 0, 0) == -1) {
9147         /* f is not seekable; probably a pipe */
9148         useList = FALSE;
9149     }
9150     if (useList && n == 0) {
9151         int error = GameListBuild(f);
9152         if (error) {
9153             DisplayError(_("Cannot build game list"), error);
9154         } else if (!ListEmpty(&gameList) &&
9155                    ((ListGame *) gameList.tailPred)->number > 1) {
9156             GameListPopUp(f, title);
9157             return TRUE;
9158         }
9159         GameListDestroy();
9160         n = 1;
9161     }
9162     if (n == 0) n = 1;
9163     return LoadGame(f, n, title, FALSE);
9164 }
9165
9166
9167 void
9168 MakeRegisteredMove()
9169 {
9170     int fromX, fromY, toX, toY;
9171     char promoChar;
9172     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9173         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9174           case CMAIL_MOVE:
9175           case CMAIL_DRAW:
9176             if (appData.debugMode)
9177               fprintf(debugFP, "Restoring %s for game %d\n",
9178                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9179     
9180             thinkOutput[0] = NULLCHAR;
9181             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9182             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9183             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9184             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9185             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9186             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9187             MakeMove(fromX, fromY, toX, toY, promoChar);
9188             ShowMove(fromX, fromY, toX, toY);
9189               
9190             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9191               case MT_NONE:
9192               case MT_CHECK:
9193                 break;
9194                 
9195               case MT_CHECKMATE:
9196               case MT_STAINMATE:
9197                 if (WhiteOnMove(currentMove)) {
9198                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9199                 } else {
9200                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9201                 }
9202                 break;
9203                 
9204               case MT_STALEMATE:
9205                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9206                 break;
9207             }
9208
9209             break;
9210             
9211           case CMAIL_RESIGN:
9212             if (WhiteOnMove(currentMove)) {
9213                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9214             } else {
9215                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9216             }
9217             break;
9218             
9219           case CMAIL_ACCEPT:
9220             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9221             break;
9222               
9223           default:
9224             break;
9225         }
9226     }
9227
9228     return;
9229 }
9230
9231 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9232 int
9233 CmailLoadGame(f, gameNumber, title, useList)
9234      FILE *f;
9235      int gameNumber;
9236      char *title;
9237      int useList;
9238 {
9239     int retVal;
9240
9241     if (gameNumber > nCmailGames) {
9242         DisplayError(_("No more games in this message"), 0);
9243         return FALSE;
9244     }
9245     if (f == lastLoadGameFP) {
9246         int offset = gameNumber - lastLoadGameNumber;
9247         if (offset == 0) {
9248             cmailMsg[0] = NULLCHAR;
9249             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9250                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9251                 nCmailMovesRegistered--;
9252             }
9253             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9254             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9255                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9256             }
9257         } else {
9258             if (! RegisterMove()) return FALSE;
9259         }
9260     }
9261
9262     retVal = LoadGame(f, gameNumber, title, useList);
9263
9264     /* Make move registered during previous look at this game, if any */
9265     MakeRegisteredMove();
9266
9267     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9268         commentList[currentMove]
9269           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9270         DisplayComment(currentMove - 1, commentList[currentMove]);
9271     }
9272
9273     return retVal;
9274 }
9275
9276 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9277 int
9278 ReloadGame(offset)
9279      int offset;
9280 {
9281     int gameNumber = lastLoadGameNumber + offset;
9282     if (lastLoadGameFP == NULL) {
9283         DisplayError(_("No game has been loaded yet"), 0);
9284         return FALSE;
9285     }
9286     if (gameNumber <= 0) {
9287         DisplayError(_("Can't back up any further"), 0);
9288         return FALSE;
9289     }
9290     if (cmailMsgLoaded) {
9291         return CmailLoadGame(lastLoadGameFP, gameNumber,
9292                              lastLoadGameTitle, lastLoadGameUseList);
9293     } else {
9294         return LoadGame(lastLoadGameFP, gameNumber,
9295                         lastLoadGameTitle, lastLoadGameUseList);
9296     }
9297 }
9298
9299
9300
9301 /* Load the nth game from open file f */
9302 int
9303 LoadGame(f, gameNumber, title, useList)
9304      FILE *f;
9305      int gameNumber;
9306      char *title;
9307      int useList;
9308 {
9309     ChessMove cm;
9310     char buf[MSG_SIZ];
9311     int gn = gameNumber;
9312     ListGame *lg = NULL;
9313     int numPGNTags = 0;
9314     int err;
9315     GameMode oldGameMode;
9316     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9317
9318     if (appData.debugMode) 
9319         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9320
9321     if (gameMode == Training )
9322         SetTrainingModeOff();
9323
9324     oldGameMode = gameMode;
9325     if (gameMode != BeginningOfGame) {
9326       Reset(FALSE, TRUE);
9327     }
9328
9329     gameFileFP = f;
9330     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9331         fclose(lastLoadGameFP);
9332     }
9333
9334     if (useList) {
9335         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9336         
9337         if (lg) {
9338             fseek(f, lg->offset, 0);
9339             GameListHighlight(gameNumber);
9340             gn = 1;
9341         }
9342         else {
9343             DisplayError(_("Game number out of range"), 0);
9344             return FALSE;
9345         }
9346     } else {
9347         GameListDestroy();
9348         if (fseek(f, 0, 0) == -1) {
9349             if (f == lastLoadGameFP ?
9350                 gameNumber == lastLoadGameNumber + 1 :
9351                 gameNumber == 1) {
9352                 gn = 1;
9353             } else {
9354                 DisplayError(_("Can't seek on game file"), 0);
9355                 return FALSE;
9356             }
9357         }
9358     }
9359     lastLoadGameFP = f;
9360     lastLoadGameNumber = gameNumber;
9361     strcpy(lastLoadGameTitle, title);
9362     lastLoadGameUseList = useList;
9363
9364     yynewfile(f);
9365
9366     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9367       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9368                 lg->gameInfo.black);
9369             DisplayTitle(buf);
9370     } else if (*title != NULLCHAR) {
9371         if (gameNumber > 1) {
9372             sprintf(buf, "%s %d", title, gameNumber);
9373             DisplayTitle(buf);
9374         } else {
9375             DisplayTitle(title);
9376         }
9377     }
9378
9379     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9380         gameMode = PlayFromGameFile;
9381         ModeHighlight();
9382     }
9383
9384     currentMove = forwardMostMove = backwardMostMove = 0;
9385     CopyBoard(boards[0], initialPosition);
9386     StopClocks();
9387
9388     /*
9389      * Skip the first gn-1 games in the file.
9390      * Also skip over anything that precedes an identifiable 
9391      * start of game marker, to avoid being confused by 
9392      * garbage at the start of the file.  Currently 
9393      * recognized start of game markers are the move number "1",
9394      * the pattern "gnuchess .* game", the pattern
9395      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9396      * A game that starts with one of the latter two patterns
9397      * will also have a move number 1, possibly
9398      * following a position diagram.
9399      * 5-4-02: Let's try being more lenient and allowing a game to
9400      * start with an unnumbered move.  Does that break anything?
9401      */
9402     cm = lastLoadGameStart = (ChessMove) 0;
9403     while (gn > 0) {
9404         yyboardindex = forwardMostMove;
9405         cm = (ChessMove) yylex();
9406         switch (cm) {
9407           case (ChessMove) 0:
9408             if (cmailMsgLoaded) {
9409                 nCmailGames = CMAIL_MAX_GAMES - gn;
9410             } else {
9411                 Reset(TRUE, TRUE);
9412                 DisplayError(_("Game not found in file"), 0);
9413             }
9414             return FALSE;
9415
9416           case GNUChessGame:
9417           case XBoardGame:
9418             gn--;
9419             lastLoadGameStart = cm;
9420             break;
9421             
9422           case MoveNumberOne:
9423             switch (lastLoadGameStart) {
9424               case GNUChessGame:
9425               case XBoardGame:
9426               case PGNTag:
9427                 break;
9428               case MoveNumberOne:
9429               case (ChessMove) 0:
9430                 gn--;           /* count this game */
9431                 lastLoadGameStart = cm;
9432                 break;
9433               default:
9434                 /* impossible */
9435                 break;
9436             }
9437             break;
9438
9439           case PGNTag:
9440             switch (lastLoadGameStart) {
9441               case GNUChessGame:
9442               case PGNTag:
9443               case MoveNumberOne:
9444               case (ChessMove) 0:
9445                 gn--;           /* count this game */
9446                 lastLoadGameStart = cm;
9447                 break;
9448               case XBoardGame:
9449                 lastLoadGameStart = cm; /* game counted already */
9450                 break;
9451               default:
9452                 /* impossible */
9453                 break;
9454             }
9455             if (gn > 0) {
9456                 do {
9457                     yyboardindex = forwardMostMove;
9458                     cm = (ChessMove) yylex();
9459                 } while (cm == PGNTag || cm == Comment);
9460             }
9461             break;
9462
9463           case WhiteWins:
9464           case BlackWins:
9465           case GameIsDrawn:
9466             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9467                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9468                     != CMAIL_OLD_RESULT) {
9469                     nCmailResults ++ ;
9470                     cmailResult[  CMAIL_MAX_GAMES
9471                                 - gn - 1] = CMAIL_OLD_RESULT;
9472                 }
9473             }
9474             break;
9475
9476           case NormalMove:
9477             /* Only a NormalMove can be at the start of a game
9478              * without a position diagram. */
9479             if (lastLoadGameStart == (ChessMove) 0) {
9480               gn--;
9481               lastLoadGameStart = MoveNumberOne;
9482             }
9483             break;
9484
9485           default:
9486             break;
9487         }
9488     }
9489     
9490     if (appData.debugMode)
9491       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9492
9493     if (cm == XBoardGame) {
9494         /* Skip any header junk before position diagram and/or move 1 */
9495         for (;;) {
9496             yyboardindex = forwardMostMove;
9497             cm = (ChessMove) yylex();
9498
9499             if (cm == (ChessMove) 0 ||
9500                 cm == GNUChessGame || cm == XBoardGame) {
9501                 /* Empty game; pretend end-of-file and handle later */
9502                 cm = (ChessMove) 0;
9503                 break;
9504             }
9505
9506             if (cm == MoveNumberOne || cm == PositionDiagram ||
9507                 cm == PGNTag || cm == Comment)
9508               break;
9509         }
9510     } else if (cm == GNUChessGame) {
9511         if (gameInfo.event != NULL) {
9512             free(gameInfo.event);
9513         }
9514         gameInfo.event = StrSave(yy_text);
9515     }   
9516
9517     startedFromSetupPosition = FALSE;
9518     while (cm == PGNTag) {
9519         if (appData.debugMode) 
9520           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9521         err = ParsePGNTag(yy_text, &gameInfo);
9522         if (!err) numPGNTags++;
9523
9524         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9525         if(gameInfo.variant != oldVariant) {
9526             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9527             InitPosition(TRUE);
9528             oldVariant = gameInfo.variant;
9529             if (appData.debugMode) 
9530               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9531         }
9532
9533
9534         if (gameInfo.fen != NULL) {
9535           Board initial_position;
9536           startedFromSetupPosition = TRUE;
9537           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9538             Reset(TRUE, TRUE);
9539             DisplayError(_("Bad FEN position in file"), 0);
9540             return FALSE;
9541           }
9542           CopyBoard(boards[0], initial_position);
9543           if (blackPlaysFirst) {
9544             currentMove = forwardMostMove = backwardMostMove = 1;
9545             CopyBoard(boards[1], initial_position);
9546             strcpy(moveList[0], "");
9547             strcpy(parseList[0], "");
9548             timeRemaining[0][1] = whiteTimeRemaining;
9549             timeRemaining[1][1] = blackTimeRemaining;
9550             if (commentList[0] != NULL) {
9551               commentList[1] = commentList[0];
9552               commentList[0] = NULL;
9553             }
9554           } else {
9555             currentMove = forwardMostMove = backwardMostMove = 0;
9556           }
9557           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9558           {   int i;
9559               initialRulePlies = FENrulePlies;
9560               for( i=0; i< nrCastlingRights; i++ )
9561                   initialRights[i] = initial_position[CASTLING][i];
9562           }
9563           yyboardindex = forwardMostMove;
9564           free(gameInfo.fen);
9565           gameInfo.fen = NULL;
9566         }
9567
9568         yyboardindex = forwardMostMove;
9569         cm = (ChessMove) yylex();
9570
9571         /* Handle comments interspersed among the tags */
9572         while (cm == Comment) {
9573             char *p;
9574             if (appData.debugMode) 
9575               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9576             p = yy_text;
9577             AppendComment(currentMove, p, FALSE);
9578             yyboardindex = forwardMostMove;
9579             cm = (ChessMove) yylex();
9580         }
9581     }
9582
9583     /* don't rely on existence of Event tag since if game was
9584      * pasted from clipboard the Event tag may not exist
9585      */
9586     if (numPGNTags > 0){
9587         char *tags;
9588         if (gameInfo.variant == VariantNormal) {
9589           gameInfo.variant = StringToVariant(gameInfo.event);
9590         }
9591         if (!matchMode) {
9592           if( appData.autoDisplayTags ) {
9593             tags = PGNTags(&gameInfo);
9594             TagsPopUp(tags, CmailMsg());
9595             free(tags);
9596           }
9597         }
9598     } else {
9599         /* Make something up, but don't display it now */
9600         SetGameInfo();
9601         TagsPopDown();
9602     }
9603
9604     if (cm == PositionDiagram) {
9605         int i, j;
9606         char *p;
9607         Board initial_position;
9608
9609         if (appData.debugMode)
9610           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9611
9612         if (!startedFromSetupPosition) {
9613             p = yy_text;
9614             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9615               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9616                 switch (*p) {
9617                   case '[':
9618                   case '-':
9619                   case ' ':
9620                   case '\t':
9621                   case '\n':
9622                   case '\r':
9623                     break;
9624                   default:
9625                     initial_position[i][j++] = CharToPiece(*p);
9626                     break;
9627                 }
9628             while (*p == ' ' || *p == '\t' ||
9629                    *p == '\n' || *p == '\r') p++;
9630         
9631             if (strncmp(p, "black", strlen("black"))==0)
9632               blackPlaysFirst = TRUE;
9633             else
9634               blackPlaysFirst = FALSE;
9635             startedFromSetupPosition = TRUE;
9636         
9637             CopyBoard(boards[0], initial_position);
9638             if (blackPlaysFirst) {
9639                 currentMove = forwardMostMove = backwardMostMove = 1;
9640                 CopyBoard(boards[1], initial_position);
9641                 strcpy(moveList[0], "");
9642                 strcpy(parseList[0], "");
9643                 timeRemaining[0][1] = whiteTimeRemaining;
9644                 timeRemaining[1][1] = blackTimeRemaining;
9645                 if (commentList[0] != NULL) {
9646                     commentList[1] = commentList[0];
9647                     commentList[0] = NULL;
9648                 }
9649             } else {
9650                 currentMove = forwardMostMove = backwardMostMove = 0;
9651             }
9652         }
9653         yyboardindex = forwardMostMove;
9654         cm = (ChessMove) yylex();
9655     }
9656
9657     if (first.pr == NoProc) {
9658         StartChessProgram(&first);
9659     }
9660     InitChessProgram(&first, FALSE);
9661     SendToProgram("force\n", &first);
9662     if (startedFromSetupPosition) {
9663         SendBoard(&first, forwardMostMove);
9664     if (appData.debugMode) {
9665         fprintf(debugFP, "Load Game\n");
9666     }
9667         DisplayBothClocks();
9668     }      
9669
9670     /* [HGM] server: flag to write setup moves in broadcast file as one */
9671     loadFlag = appData.suppressLoadMoves;
9672
9673     while (cm == Comment) {
9674         char *p;
9675         if (appData.debugMode) 
9676           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9677         p = yy_text;
9678         AppendComment(currentMove, p, FALSE);
9679         yyboardindex = forwardMostMove;
9680         cm = (ChessMove) yylex();
9681     }
9682
9683     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9684         cm == WhiteWins || cm == BlackWins ||
9685         cm == GameIsDrawn || cm == GameUnfinished) {
9686         DisplayMessage("", _("No moves in game"));
9687         if (cmailMsgLoaded) {
9688             if (appData.debugMode)
9689               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9690             ClearHighlights();
9691             flipView = FALSE;
9692         }
9693         DrawPosition(FALSE, boards[currentMove]);
9694         DisplayBothClocks();
9695         gameMode = EditGame;
9696         ModeHighlight();
9697         gameFileFP = NULL;
9698         cmailOldMove = 0;
9699         return TRUE;
9700     }
9701
9702     // [HGM] PV info: routine tests if comment empty
9703     if (!matchMode && (pausing || appData.timeDelay != 0)) {
9704         DisplayComment(currentMove - 1, commentList[currentMove]);
9705     }
9706     if (!matchMode && appData.timeDelay != 0) 
9707       DrawPosition(FALSE, boards[currentMove]);
9708
9709     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9710       programStats.ok_to_send = 1;
9711     }
9712
9713     /* if the first token after the PGN tags is a move
9714      * and not move number 1, retrieve it from the parser 
9715      */
9716     if (cm != MoveNumberOne)
9717         LoadGameOneMove(cm);
9718
9719     /* load the remaining moves from the file */
9720     while (LoadGameOneMove((ChessMove)0)) {
9721       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9722       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9723     }
9724
9725     /* rewind to the start of the game */
9726     currentMove = backwardMostMove;
9727
9728     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9729
9730     if (oldGameMode == AnalyzeFile ||
9731         oldGameMode == AnalyzeMode) {
9732       AnalyzeFileEvent();
9733     }
9734
9735     if (matchMode || appData.timeDelay == 0) {
9736       ToEndEvent();
9737       gameMode = EditGame;
9738       ModeHighlight();
9739     } else if (appData.timeDelay > 0) {
9740       AutoPlayGameLoop();
9741     }
9742
9743     if (appData.debugMode) 
9744         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9745
9746     loadFlag = 0; /* [HGM] true game starts */
9747     return TRUE;
9748 }
9749
9750 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9751 int
9752 ReloadPosition(offset)
9753      int offset;
9754 {
9755     int positionNumber = lastLoadPositionNumber + offset;
9756     if (lastLoadPositionFP == NULL) {
9757         DisplayError(_("No position has been loaded yet"), 0);
9758         return FALSE;
9759     }
9760     if (positionNumber <= 0) {
9761         DisplayError(_("Can't back up any further"), 0);
9762         return FALSE;
9763     }
9764     return LoadPosition(lastLoadPositionFP, positionNumber,
9765                         lastLoadPositionTitle);
9766 }
9767
9768 /* Load the nth position from the given file */
9769 int
9770 LoadPositionFromFile(filename, n, title)
9771      char *filename;
9772      int n;
9773      char *title;
9774 {
9775     FILE *f;
9776     char buf[MSG_SIZ];
9777
9778     if (strcmp(filename, "-") == 0) {
9779         return LoadPosition(stdin, n, "stdin");
9780     } else {
9781         f = fopen(filename, "rb");
9782         if (f == NULL) {
9783             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9784             DisplayError(buf, errno);
9785             return FALSE;
9786         } else {
9787             return LoadPosition(f, n, title);
9788         }
9789     }
9790 }
9791
9792 /* Load the nth position from the given open file, and close it */
9793 int
9794 LoadPosition(f, positionNumber, title)
9795      FILE *f;
9796      int positionNumber;
9797      char *title;
9798 {
9799     char *p, line[MSG_SIZ];
9800     Board initial_position;
9801     int i, j, fenMode, pn;
9802     
9803     if (gameMode == Training )
9804         SetTrainingModeOff();
9805
9806     if (gameMode != BeginningOfGame) {
9807         Reset(FALSE, TRUE);
9808     }
9809     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9810         fclose(lastLoadPositionFP);
9811     }
9812     if (positionNumber == 0) positionNumber = 1;
9813     lastLoadPositionFP = f;
9814     lastLoadPositionNumber = positionNumber;
9815     strcpy(lastLoadPositionTitle, title);
9816     if (first.pr == NoProc) {
9817       StartChessProgram(&first);
9818       InitChessProgram(&first, FALSE);
9819     }    
9820     pn = positionNumber;
9821     if (positionNumber < 0) {
9822         /* Negative position number means to seek to that byte offset */
9823         if (fseek(f, -positionNumber, 0) == -1) {
9824             DisplayError(_("Can't seek on position file"), 0);
9825             return FALSE;
9826         };
9827         pn = 1;
9828     } else {
9829         if (fseek(f, 0, 0) == -1) {
9830             if (f == lastLoadPositionFP ?
9831                 positionNumber == lastLoadPositionNumber + 1 :
9832                 positionNumber == 1) {
9833                 pn = 1;
9834             } else {
9835                 DisplayError(_("Can't seek on position file"), 0);
9836                 return FALSE;
9837             }
9838         }
9839     }
9840     /* See if this file is FEN or old-style xboard */
9841     if (fgets(line, MSG_SIZ, f) == NULL) {
9842         DisplayError(_("Position not found in file"), 0);
9843         return FALSE;
9844     }
9845     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9846     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9847
9848     if (pn >= 2) {
9849         if (fenMode || line[0] == '#') pn--;
9850         while (pn > 0) {
9851             /* skip positions before number pn */
9852             if (fgets(line, MSG_SIZ, f) == NULL) {
9853                 Reset(TRUE, TRUE);
9854                 DisplayError(_("Position not found in file"), 0);
9855                 return FALSE;
9856             }
9857             if (fenMode || line[0] == '#') pn--;
9858         }
9859     }
9860
9861     if (fenMode) {
9862         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9863             DisplayError(_("Bad FEN position in file"), 0);
9864             return FALSE;
9865         }
9866     } else {
9867         (void) fgets(line, MSG_SIZ, f);
9868         (void) fgets(line, MSG_SIZ, f);
9869     
9870         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9871             (void) fgets(line, MSG_SIZ, f);
9872             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9873                 if (*p == ' ')
9874                   continue;
9875                 initial_position[i][j++] = CharToPiece(*p);
9876             }
9877         }
9878     
9879         blackPlaysFirst = FALSE;
9880         if (!feof(f)) {
9881             (void) fgets(line, MSG_SIZ, f);
9882             if (strncmp(line, "black", strlen("black"))==0)
9883               blackPlaysFirst = TRUE;
9884         }
9885     }
9886     startedFromSetupPosition = TRUE;
9887     
9888     SendToProgram("force\n", &first);
9889     CopyBoard(boards[0], initial_position);
9890     if (blackPlaysFirst) {
9891         currentMove = forwardMostMove = backwardMostMove = 1;
9892         strcpy(moveList[0], "");
9893         strcpy(parseList[0], "");
9894         CopyBoard(boards[1], initial_position);
9895         DisplayMessage("", _("Black to play"));
9896     } else {
9897         currentMove = forwardMostMove = backwardMostMove = 0;
9898         DisplayMessage("", _("White to play"));
9899     }
9900     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9901     SendBoard(&first, forwardMostMove);
9902     if (appData.debugMode) {
9903 int i, j;
9904   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9905   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9906         fprintf(debugFP, "Load Position\n");
9907     }
9908
9909     if (positionNumber > 1) {
9910         sprintf(line, "%s %d", title, positionNumber);
9911         DisplayTitle(line);
9912     } else {
9913         DisplayTitle(title);
9914     }
9915     gameMode = EditGame;
9916     ModeHighlight();
9917     ResetClocks();
9918     timeRemaining[0][1] = whiteTimeRemaining;
9919     timeRemaining[1][1] = blackTimeRemaining;
9920     DrawPosition(FALSE, boards[currentMove]);
9921    
9922     return TRUE;
9923 }
9924
9925
9926 void
9927 CopyPlayerNameIntoFileName(dest, src)
9928      char **dest, *src;
9929 {
9930     while (*src != NULLCHAR && *src != ',') {
9931         if (*src == ' ') {
9932             *(*dest)++ = '_';
9933             src++;
9934         } else {
9935             *(*dest)++ = *src++;
9936         }
9937     }
9938 }
9939
9940 char *DefaultFileName(ext)
9941      char *ext;
9942 {
9943     static char def[MSG_SIZ];
9944     char *p;
9945
9946     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9947         p = def;
9948         CopyPlayerNameIntoFileName(&p, gameInfo.white);
9949         *p++ = '-';
9950         CopyPlayerNameIntoFileName(&p, gameInfo.black);
9951         *p++ = '.';
9952         strcpy(p, ext);
9953     } else {
9954         def[0] = NULLCHAR;
9955     }
9956     return def;
9957 }
9958
9959 /* Save the current game to the given file */
9960 int
9961 SaveGameToFile(filename, append)
9962      char *filename;
9963      int append;
9964 {
9965     FILE *f;
9966     char buf[MSG_SIZ];
9967
9968     if (strcmp(filename, "-") == 0) {
9969         return SaveGame(stdout, 0, NULL);
9970     } else {
9971         f = fopen(filename, append ? "a" : "w");
9972         if (f == NULL) {
9973             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9974             DisplayError(buf, errno);
9975             return FALSE;
9976         } else {
9977             return SaveGame(f, 0, NULL);
9978         }
9979     }
9980 }
9981
9982 char *
9983 SavePart(str)
9984      char *str;
9985 {
9986     static char buf[MSG_SIZ];
9987     char *p;
9988     
9989     p = strchr(str, ' ');
9990     if (p == NULL) return str;
9991     strncpy(buf, str, p - str);
9992     buf[p - str] = NULLCHAR;
9993     return buf;
9994 }
9995
9996 #define PGN_MAX_LINE 75
9997
9998 #define PGN_SIDE_WHITE  0
9999 #define PGN_SIDE_BLACK  1
10000
10001 /* [AS] */
10002 static int FindFirstMoveOutOfBook( int side )
10003 {
10004     int result = -1;
10005
10006     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10007         int index = backwardMostMove;
10008         int has_book_hit = 0;
10009
10010         if( (index % 2) != side ) {
10011             index++;
10012         }
10013
10014         while( index < forwardMostMove ) {
10015             /* Check to see if engine is in book */
10016             int depth = pvInfoList[index].depth;
10017             int score = pvInfoList[index].score;
10018             int in_book = 0;
10019
10020             if( depth <= 2 ) {
10021                 in_book = 1;
10022             }
10023             else if( score == 0 && depth == 63 ) {
10024                 in_book = 1; /* Zappa */
10025             }
10026             else if( score == 2 && depth == 99 ) {
10027                 in_book = 1; /* Abrok */
10028             }
10029
10030             has_book_hit += in_book;
10031
10032             if( ! in_book ) {
10033                 result = index;
10034
10035                 break;
10036             }
10037
10038             index += 2;
10039         }
10040     }
10041
10042     return result;
10043 }
10044
10045 /* [AS] */
10046 void GetOutOfBookInfo( char * buf )
10047 {
10048     int oob[2];
10049     int i;
10050     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10051
10052     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10053     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10054
10055     *buf = '\0';
10056
10057     if( oob[0] >= 0 || oob[1] >= 0 ) {
10058         for( i=0; i<2; i++ ) {
10059             int idx = oob[i];
10060
10061             if( idx >= 0 ) {
10062                 if( i > 0 && oob[0] >= 0 ) {
10063                     strcat( buf, "   " );
10064                 }
10065
10066                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10067                 sprintf( buf+strlen(buf), "%s%.2f", 
10068                     pvInfoList[idx].score >= 0 ? "+" : "",
10069                     pvInfoList[idx].score / 100.0 );
10070             }
10071         }
10072     }
10073 }
10074
10075 /* Save game in PGN style and close the file */
10076 int
10077 SaveGamePGN(f)
10078      FILE *f;
10079 {
10080     int i, offset, linelen, newblock;
10081     time_t tm;
10082 //    char *movetext;
10083     char numtext[32];
10084     int movelen, numlen, blank;
10085     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10086
10087     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10088     
10089     tm = time((time_t *) NULL);
10090     
10091     PrintPGNTags(f, &gameInfo);
10092     
10093     if (backwardMostMove > 0 || startedFromSetupPosition) {
10094         char *fen = PositionToFEN(backwardMostMove, NULL);
10095         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10096         fprintf(f, "\n{--------------\n");
10097         PrintPosition(f, backwardMostMove);
10098         fprintf(f, "--------------}\n");
10099         free(fen);
10100     }
10101     else {
10102         /* [AS] Out of book annotation */
10103         if( appData.saveOutOfBookInfo ) {
10104             char buf[64];
10105
10106             GetOutOfBookInfo( buf );
10107
10108             if( buf[0] != '\0' ) {
10109                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10110             }
10111         }
10112
10113         fprintf(f, "\n");
10114     }
10115
10116     i = backwardMostMove;
10117     linelen = 0;
10118     newblock = TRUE;
10119
10120     while (i < forwardMostMove) {
10121         /* Print comments preceding this move */
10122         if (commentList[i] != NULL) {
10123             if (linelen > 0) fprintf(f, "\n");
10124             fprintf(f, "%s", commentList[i]);
10125             linelen = 0;
10126             newblock = TRUE;
10127         }
10128
10129         /* Format move number */
10130         if ((i % 2) == 0) {
10131             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10132         } else {
10133             if (newblock) {
10134                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10135             } else {
10136                 numtext[0] = NULLCHAR;
10137             }
10138         }
10139         numlen = strlen(numtext);
10140         newblock = FALSE;
10141
10142         /* Print move number */
10143         blank = linelen > 0 && numlen > 0;
10144         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10145             fprintf(f, "\n");
10146             linelen = 0;
10147             blank = 0;
10148         }
10149         if (blank) {
10150             fprintf(f, " ");
10151             linelen++;
10152         }
10153         fprintf(f, "%s", numtext);
10154         linelen += numlen;
10155
10156         /* Get move */
10157         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10158         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10159
10160         /* Print move */
10161         blank = linelen > 0 && movelen > 0;
10162         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10163             fprintf(f, "\n");
10164             linelen = 0;
10165             blank = 0;
10166         }
10167         if (blank) {
10168             fprintf(f, " ");
10169             linelen++;
10170         }
10171         fprintf(f, "%s", move_buffer);
10172         linelen += movelen;
10173
10174         /* [AS] Add PV info if present */
10175         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10176             /* [HGM] add time */
10177             char buf[MSG_SIZ]; int seconds;
10178
10179             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10180
10181             if( seconds <= 0) buf[0] = 0; else
10182             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10183                 seconds = (seconds + 4)/10; // round to full seconds
10184                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10185                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10186             }
10187
10188             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10189                 pvInfoList[i].score >= 0 ? "+" : "",
10190                 pvInfoList[i].score / 100.0,
10191                 pvInfoList[i].depth,
10192                 buf );
10193
10194             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10195
10196             /* Print score/depth */
10197             blank = linelen > 0 && movelen > 0;
10198             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10199                 fprintf(f, "\n");
10200                 linelen = 0;
10201                 blank = 0;
10202             }
10203             if (blank) {
10204                 fprintf(f, " ");
10205                 linelen++;
10206             }
10207             fprintf(f, "%s", move_buffer);
10208             linelen += movelen;
10209         }
10210
10211         i++;
10212     }
10213     
10214     /* Start a new line */
10215     if (linelen > 0) fprintf(f, "\n");
10216
10217     /* Print comments after last move */
10218     if (commentList[i] != NULL) {
10219         fprintf(f, "%s\n", commentList[i]);
10220     }
10221
10222     /* Print result */
10223     if (gameInfo.resultDetails != NULL &&
10224         gameInfo.resultDetails[0] != NULLCHAR) {
10225         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10226                 PGNResult(gameInfo.result));
10227     } else {
10228         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10229     }
10230
10231     fclose(f);
10232     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10233     return TRUE;
10234 }
10235
10236 /* Save game in old style and close the file */
10237 int
10238 SaveGameOldStyle(f)
10239      FILE *f;
10240 {
10241     int i, offset;
10242     time_t tm;
10243     
10244     tm = time((time_t *) NULL);
10245     
10246     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10247     PrintOpponents(f);
10248     
10249     if (backwardMostMove > 0 || startedFromSetupPosition) {
10250         fprintf(f, "\n[--------------\n");
10251         PrintPosition(f, backwardMostMove);
10252         fprintf(f, "--------------]\n");
10253     } else {
10254         fprintf(f, "\n");
10255     }
10256
10257     i = backwardMostMove;
10258     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10259
10260     while (i < forwardMostMove) {
10261         if (commentList[i] != NULL) {
10262             fprintf(f, "[%s]\n", commentList[i]);
10263         }
10264
10265         if ((i % 2) == 1) {
10266             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10267             i++;
10268         } else {
10269             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10270             i++;
10271             if (commentList[i] != NULL) {
10272                 fprintf(f, "\n");
10273                 continue;
10274             }
10275             if (i >= forwardMostMove) {
10276                 fprintf(f, "\n");
10277                 break;
10278             }
10279             fprintf(f, "%s\n", parseList[i]);
10280             i++;
10281         }
10282     }
10283     
10284     if (commentList[i] != NULL) {
10285         fprintf(f, "[%s]\n", commentList[i]);
10286     }
10287
10288     /* This isn't really the old style, but it's close enough */
10289     if (gameInfo.resultDetails != NULL &&
10290         gameInfo.resultDetails[0] != NULLCHAR) {
10291         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10292                 gameInfo.resultDetails);
10293     } else {
10294         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10295     }
10296
10297     fclose(f);
10298     return TRUE;
10299 }
10300
10301 /* Save the current game to open file f and close the file */
10302 int
10303 SaveGame(f, dummy, dummy2)
10304      FILE *f;
10305      int dummy;
10306      char *dummy2;
10307 {
10308     if (gameMode == EditPosition) EditPositionDone(TRUE);
10309     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10310     if (appData.oldSaveStyle)
10311       return SaveGameOldStyle(f);
10312     else
10313       return SaveGamePGN(f);
10314 }
10315
10316 /* Save the current position to the given file */
10317 int
10318 SavePositionToFile(filename)
10319      char *filename;
10320 {
10321     FILE *f;
10322     char buf[MSG_SIZ];
10323
10324     if (strcmp(filename, "-") == 0) {
10325         return SavePosition(stdout, 0, NULL);
10326     } else {
10327         f = fopen(filename, "a");
10328         if (f == NULL) {
10329             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10330             DisplayError(buf, errno);
10331             return FALSE;
10332         } else {
10333             SavePosition(f, 0, NULL);
10334             return TRUE;
10335         }
10336     }
10337 }
10338
10339 /* Save the current position to the given open file and close the file */
10340 int
10341 SavePosition(f, dummy, dummy2)
10342      FILE *f;
10343      int dummy;
10344      char *dummy2;
10345 {
10346     time_t tm;
10347     char *fen;
10348     
10349     if (gameMode == EditPosition) EditPositionDone(TRUE);
10350     if (appData.oldSaveStyle) {
10351         tm = time((time_t *) NULL);
10352     
10353         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10354         PrintOpponents(f);
10355         fprintf(f, "[--------------\n");
10356         PrintPosition(f, currentMove);
10357         fprintf(f, "--------------]\n");
10358     } else {
10359         fen = PositionToFEN(currentMove, NULL);
10360         fprintf(f, "%s\n", fen);
10361         free(fen);
10362     }
10363     fclose(f);
10364     return TRUE;
10365 }
10366
10367 void
10368 ReloadCmailMsgEvent(unregister)
10369      int unregister;
10370 {
10371 #if !WIN32
10372     static char *inFilename = NULL;
10373     static char *outFilename;
10374     int i;
10375     struct stat inbuf, outbuf;
10376     int status;
10377     
10378     /* Any registered moves are unregistered if unregister is set, */
10379     /* i.e. invoked by the signal handler */
10380     if (unregister) {
10381         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10382             cmailMoveRegistered[i] = FALSE;
10383             if (cmailCommentList[i] != NULL) {
10384                 free(cmailCommentList[i]);
10385                 cmailCommentList[i] = NULL;
10386             }
10387         }
10388         nCmailMovesRegistered = 0;
10389     }
10390
10391     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10392         cmailResult[i] = CMAIL_NOT_RESULT;
10393     }
10394     nCmailResults = 0;
10395
10396     if (inFilename == NULL) {
10397         /* Because the filenames are static they only get malloced once  */
10398         /* and they never get freed                                      */
10399         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10400         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10401
10402         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10403         sprintf(outFilename, "%s.out", appData.cmailGameName);
10404     }
10405     
10406     status = stat(outFilename, &outbuf);
10407     if (status < 0) {
10408         cmailMailedMove = FALSE;
10409     } else {
10410         status = stat(inFilename, &inbuf);
10411         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10412     }
10413     
10414     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10415        counts the games, notes how each one terminated, etc.
10416        
10417        It would be nice to remove this kludge and instead gather all
10418        the information while building the game list.  (And to keep it
10419        in the game list nodes instead of having a bunch of fixed-size
10420        parallel arrays.)  Note this will require getting each game's
10421        termination from the PGN tags, as the game list builder does
10422        not process the game moves.  --mann
10423        */
10424     cmailMsgLoaded = TRUE;
10425     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10426     
10427     /* Load first game in the file or popup game menu */
10428     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10429
10430 #endif /* !WIN32 */
10431     return;
10432 }
10433
10434 int
10435 RegisterMove()
10436 {
10437     FILE *f;
10438     char string[MSG_SIZ];
10439
10440     if (   cmailMailedMove
10441         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10442         return TRUE;            /* Allow free viewing  */
10443     }
10444
10445     /* Unregister move to ensure that we don't leave RegisterMove        */
10446     /* with the move registered when the conditions for registering no   */
10447     /* longer hold                                                       */
10448     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10449         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10450         nCmailMovesRegistered --;
10451
10452         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10453           {
10454               free(cmailCommentList[lastLoadGameNumber - 1]);
10455               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10456           }
10457     }
10458
10459     if (cmailOldMove == -1) {
10460         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10461         return FALSE;
10462     }
10463
10464     if (currentMove > cmailOldMove + 1) {
10465         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10466         return FALSE;
10467     }
10468
10469     if (currentMove < cmailOldMove) {
10470         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10471         return FALSE;
10472     }
10473
10474     if (forwardMostMove > currentMove) {
10475         /* Silently truncate extra moves */
10476         TruncateGame();
10477     }
10478
10479     if (   (currentMove == cmailOldMove + 1)
10480         || (   (currentMove == cmailOldMove)
10481             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10482                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10483         if (gameInfo.result != GameUnfinished) {
10484             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10485         }
10486
10487         if (commentList[currentMove] != NULL) {
10488             cmailCommentList[lastLoadGameNumber - 1]
10489               = StrSave(commentList[currentMove]);
10490         }
10491         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10492
10493         if (appData.debugMode)
10494           fprintf(debugFP, "Saving %s for game %d\n",
10495                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10496
10497         sprintf(string,
10498                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10499         
10500         f = fopen(string, "w");
10501         if (appData.oldSaveStyle) {
10502             SaveGameOldStyle(f); /* also closes the file */
10503             
10504             sprintf(string, "%s.pos.out", appData.cmailGameName);
10505             f = fopen(string, "w");
10506             SavePosition(f, 0, NULL); /* also closes the file */
10507         } else {
10508             fprintf(f, "{--------------\n");
10509             PrintPosition(f, currentMove);
10510             fprintf(f, "--------------}\n\n");
10511             
10512             SaveGame(f, 0, NULL); /* also closes the file*/
10513         }
10514         
10515         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10516         nCmailMovesRegistered ++;
10517     } else if (nCmailGames == 1) {
10518         DisplayError(_("You have not made a move yet"), 0);
10519         return FALSE;
10520     }
10521
10522     return TRUE;
10523 }
10524
10525 void
10526 MailMoveEvent()
10527 {
10528 #if !WIN32
10529     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10530     FILE *commandOutput;
10531     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10532     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10533     int nBuffers;
10534     int i;
10535     int archived;
10536     char *arcDir;
10537
10538     if (! cmailMsgLoaded) {
10539         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10540         return;
10541     }
10542
10543     if (nCmailGames == nCmailResults) {
10544         DisplayError(_("No unfinished games"), 0);
10545         return;
10546     }
10547
10548 #if CMAIL_PROHIBIT_REMAIL
10549     if (cmailMailedMove) {
10550         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);
10551         DisplayError(msg, 0);
10552         return;
10553     }
10554 #endif
10555
10556     if (! (cmailMailedMove || RegisterMove())) return;
10557     
10558     if (   cmailMailedMove
10559         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10560         sprintf(string, partCommandString,
10561                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10562         commandOutput = popen(string, "r");
10563
10564         if (commandOutput == NULL) {
10565             DisplayError(_("Failed to invoke cmail"), 0);
10566         } else {
10567             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10568                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10569             }
10570             if (nBuffers > 1) {
10571                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10572                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10573                 nBytes = MSG_SIZ - 1;
10574             } else {
10575                 (void) memcpy(msg, buffer, nBytes);
10576             }
10577             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10578
10579             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10580                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10581
10582                 archived = TRUE;
10583                 for (i = 0; i < nCmailGames; i ++) {
10584                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10585                         archived = FALSE;
10586                     }
10587                 }
10588                 if (   archived
10589                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10590                         != NULL)) {
10591                     sprintf(buffer, "%s/%s.%s.archive",
10592                             arcDir,
10593                             appData.cmailGameName,
10594                             gameInfo.date);
10595                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10596                     cmailMsgLoaded = FALSE;
10597                 }
10598             }
10599
10600             DisplayInformation(msg);
10601             pclose(commandOutput);
10602         }
10603     } else {
10604         if ((*cmailMsg) != '\0') {
10605             DisplayInformation(cmailMsg);
10606         }
10607     }
10608
10609     return;
10610 #endif /* !WIN32 */
10611 }
10612
10613 char *
10614 CmailMsg()
10615 {
10616 #if WIN32
10617     return NULL;
10618 #else
10619     int  prependComma = 0;
10620     char number[5];
10621     char string[MSG_SIZ];       /* Space for game-list */
10622     int  i;
10623     
10624     if (!cmailMsgLoaded) return "";
10625
10626     if (cmailMailedMove) {
10627         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10628     } else {
10629         /* Create a list of games left */
10630         sprintf(string, "[");
10631         for (i = 0; i < nCmailGames; i ++) {
10632             if (! (   cmailMoveRegistered[i]
10633                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10634                 if (prependComma) {
10635                     sprintf(number, ",%d", i + 1);
10636                 } else {
10637                     sprintf(number, "%d", i + 1);
10638                     prependComma = 1;
10639                 }
10640                 
10641                 strcat(string, number);
10642             }
10643         }
10644         strcat(string, "]");
10645
10646         if (nCmailMovesRegistered + nCmailResults == 0) {
10647             switch (nCmailGames) {
10648               case 1:
10649                 sprintf(cmailMsg,
10650                         _("Still need to make move for game\n"));
10651                 break;
10652                 
10653               case 2:
10654                 sprintf(cmailMsg,
10655                         _("Still need to make moves for both games\n"));
10656                 break;
10657                 
10658               default:
10659                 sprintf(cmailMsg,
10660                         _("Still need to make moves for all %d games\n"),
10661                         nCmailGames);
10662                 break;
10663             }
10664         } else {
10665             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10666               case 1:
10667                 sprintf(cmailMsg,
10668                         _("Still need to make a move for game %s\n"),
10669                         string);
10670                 break;
10671                 
10672               case 0:
10673                 if (nCmailResults == nCmailGames) {
10674                     sprintf(cmailMsg, _("No unfinished games\n"));
10675                 } else {
10676                     sprintf(cmailMsg, _("Ready to send mail\n"));
10677                 }
10678                 break;
10679                 
10680               default:
10681                 sprintf(cmailMsg,
10682                         _("Still need to make moves for games %s\n"),
10683                         string);
10684             }
10685         }
10686     }
10687     return cmailMsg;
10688 #endif /* WIN32 */
10689 }
10690
10691 void
10692 ResetGameEvent()
10693 {
10694     if (gameMode == Training)
10695       SetTrainingModeOff();
10696
10697     Reset(TRUE, TRUE);
10698     cmailMsgLoaded = FALSE;
10699     if (appData.icsActive) {
10700       SendToICS(ics_prefix);
10701       SendToICS("refresh\n");
10702     }
10703 }
10704
10705 void
10706 ExitEvent(status)
10707      int status;
10708 {
10709     exiting++;
10710     if (exiting > 2) {
10711       /* Give up on clean exit */
10712       exit(status);
10713     }
10714     if (exiting > 1) {
10715       /* Keep trying for clean exit */
10716       return;
10717     }
10718
10719     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10720
10721     if (telnetISR != NULL) {
10722       RemoveInputSource(telnetISR);
10723     }
10724     if (icsPR != NoProc) {
10725       DestroyChildProcess(icsPR, TRUE);
10726     }
10727
10728     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10729     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10730
10731     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10732     /* make sure this other one finishes before killing it!                  */
10733     if(endingGame) { int count = 0;
10734         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10735         while(endingGame && count++ < 10) DoSleep(1);
10736         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10737     }
10738
10739     /* Kill off chess programs */
10740     if (first.pr != NoProc) {
10741         ExitAnalyzeMode();
10742         
10743         DoSleep( appData.delayBeforeQuit );
10744         SendToProgram("quit\n", &first);
10745         DoSleep( appData.delayAfterQuit );
10746         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10747     }
10748     if (second.pr != NoProc) {
10749         DoSleep( appData.delayBeforeQuit );
10750         SendToProgram("quit\n", &second);
10751         DoSleep( appData.delayAfterQuit );
10752         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10753     }
10754     if (first.isr != NULL) {
10755         RemoveInputSource(first.isr);
10756     }
10757     if (second.isr != NULL) {
10758         RemoveInputSource(second.isr);
10759     }
10760
10761     ShutDownFrontEnd();
10762     exit(status);
10763 }
10764
10765 void
10766 PauseEvent()
10767 {
10768     if (appData.debugMode)
10769         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10770     if (pausing) {
10771         pausing = FALSE;
10772         ModeHighlight();
10773         if (gameMode == MachinePlaysWhite ||
10774             gameMode == MachinePlaysBlack) {
10775             StartClocks();
10776         } else {
10777             DisplayBothClocks();
10778         }
10779         if (gameMode == PlayFromGameFile) {
10780             if (appData.timeDelay >= 0) 
10781                 AutoPlayGameLoop();
10782         } else if (gameMode == IcsExamining && pauseExamInvalid) {
10783             Reset(FALSE, TRUE);
10784             SendToICS(ics_prefix);
10785             SendToICS("refresh\n");
10786         } else if (currentMove < forwardMostMove) {
10787             ForwardInner(forwardMostMove);
10788         }
10789         pauseExamInvalid = FALSE;
10790     } else {
10791         switch (gameMode) {
10792           default:
10793             return;
10794           case IcsExamining:
10795             pauseExamForwardMostMove = forwardMostMove;
10796             pauseExamInvalid = FALSE;
10797             /* fall through */
10798           case IcsObserving:
10799           case IcsPlayingWhite:
10800           case IcsPlayingBlack:
10801             pausing = TRUE;
10802             ModeHighlight();
10803             return;
10804           case PlayFromGameFile:
10805             (void) StopLoadGameTimer();
10806             pausing = TRUE;
10807             ModeHighlight();
10808             break;
10809           case BeginningOfGame:
10810             if (appData.icsActive) return;
10811             /* else fall through */
10812           case MachinePlaysWhite:
10813           case MachinePlaysBlack:
10814           case TwoMachinesPlay:
10815             if (forwardMostMove == 0)
10816               return;           /* don't pause if no one has moved */
10817             if ((gameMode == MachinePlaysWhite &&
10818                  !WhiteOnMove(forwardMostMove)) ||
10819                 (gameMode == MachinePlaysBlack &&
10820                  WhiteOnMove(forwardMostMove))) {
10821                 StopClocks();
10822             }
10823             pausing = TRUE;
10824             ModeHighlight();
10825             break;
10826         }
10827     }
10828 }
10829
10830 void
10831 EditCommentEvent()
10832 {
10833     char title[MSG_SIZ];
10834
10835     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10836         strcpy(title, _("Edit comment"));
10837     } else {
10838         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10839                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10840                 parseList[currentMove - 1]);
10841     }
10842
10843     EditCommentPopUp(currentMove, title, commentList[currentMove]);
10844 }
10845
10846
10847 void
10848 EditTagsEvent()
10849 {
10850     char *tags = PGNTags(&gameInfo);
10851     EditTagsPopUp(tags);
10852     free(tags);
10853 }
10854
10855 void
10856 AnalyzeModeEvent()
10857 {
10858     if (appData.noChessProgram || gameMode == AnalyzeMode)
10859       return;
10860
10861     if (gameMode != AnalyzeFile) {
10862         if (!appData.icsEngineAnalyze) {
10863                EditGameEvent();
10864                if (gameMode != EditGame) return;
10865         }
10866         ResurrectChessProgram();
10867         SendToProgram("analyze\n", &first);
10868         first.analyzing = TRUE;
10869         /*first.maybeThinking = TRUE;*/
10870         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10871         EngineOutputPopUp();
10872     }
10873     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10874     pausing = FALSE;
10875     ModeHighlight();
10876     SetGameInfo();
10877
10878     StartAnalysisClock();
10879     GetTimeMark(&lastNodeCountTime);
10880     lastNodeCount = 0;
10881 }
10882
10883 void
10884 AnalyzeFileEvent()
10885 {
10886     if (appData.noChessProgram || gameMode == AnalyzeFile)
10887       return;
10888
10889     if (gameMode != AnalyzeMode) {
10890         EditGameEvent();
10891         if (gameMode != EditGame) return;
10892         ResurrectChessProgram();
10893         SendToProgram("analyze\n", &first);
10894         first.analyzing = TRUE;
10895         /*first.maybeThinking = TRUE;*/
10896         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10897         EngineOutputPopUp();
10898     }
10899     gameMode = AnalyzeFile;
10900     pausing = FALSE;
10901     ModeHighlight();
10902     SetGameInfo();
10903
10904     StartAnalysisClock();
10905     GetTimeMark(&lastNodeCountTime);
10906     lastNodeCount = 0;
10907 }
10908
10909 void
10910 MachineWhiteEvent()
10911 {
10912     char buf[MSG_SIZ];
10913     char *bookHit = NULL;
10914
10915     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10916       return;
10917
10918
10919     if (gameMode == PlayFromGameFile || 
10920         gameMode == TwoMachinesPlay  || 
10921         gameMode == Training         || 
10922         gameMode == AnalyzeMode      || 
10923         gameMode == EndOfGame)
10924         EditGameEvent();
10925
10926     if (gameMode == EditPosition) 
10927         EditPositionDone(TRUE);
10928
10929     if (!WhiteOnMove(currentMove)) {
10930         DisplayError(_("It is not White's turn"), 0);
10931         return;
10932     }
10933   
10934     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10935       ExitAnalyzeMode();
10936
10937     if (gameMode == EditGame || gameMode == AnalyzeMode || 
10938         gameMode == AnalyzeFile)
10939         TruncateGame();
10940
10941     ResurrectChessProgram();    /* in case it isn't running */
10942     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10943         gameMode = MachinePlaysWhite;
10944         ResetClocks();
10945     } else
10946     gameMode = MachinePlaysWhite;
10947     pausing = FALSE;
10948     ModeHighlight();
10949     SetGameInfo();
10950     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10951     DisplayTitle(buf);
10952     if (first.sendName) {
10953       sprintf(buf, "name %s\n", gameInfo.black);
10954       SendToProgram(buf, &first);
10955     }
10956     if (first.sendTime) {
10957       if (first.useColors) {
10958         SendToProgram("black\n", &first); /*gnu kludge*/
10959       }
10960       SendTimeRemaining(&first, TRUE);
10961     }
10962     if (first.useColors) {
10963       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10964     }
10965     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10966     SetMachineThinkingEnables();
10967     first.maybeThinking = TRUE;
10968     StartClocks();
10969     firstMove = FALSE;
10970
10971     if (appData.autoFlipView && !flipView) {
10972       flipView = !flipView;
10973       DrawPosition(FALSE, NULL);
10974       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
10975     }
10976
10977     if(bookHit) { // [HGM] book: simulate book reply
10978         static char bookMove[MSG_SIZ]; // a bit generous?
10979
10980         programStats.nodes = programStats.depth = programStats.time = 
10981         programStats.score = programStats.got_only_move = 0;
10982         sprintf(programStats.movelist, "%s (xbook)", bookHit);
10983
10984         strcpy(bookMove, "move ");
10985         strcat(bookMove, bookHit);
10986         HandleMachineMove(bookMove, &first);
10987     }
10988 }
10989
10990 void
10991 MachineBlackEvent()
10992 {
10993     char buf[MSG_SIZ];
10994    char *bookHit = NULL;
10995
10996     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10997         return;
10998
10999
11000     if (gameMode == PlayFromGameFile || 
11001         gameMode == TwoMachinesPlay  || 
11002         gameMode == Training         || 
11003         gameMode == AnalyzeMode      || 
11004         gameMode == EndOfGame)
11005         EditGameEvent();
11006
11007     if (gameMode == EditPosition) 
11008         EditPositionDone(TRUE);
11009
11010     if (WhiteOnMove(currentMove)) {
11011         DisplayError(_("It is not Black's turn"), 0);
11012         return;
11013     }
11014     
11015     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11016       ExitAnalyzeMode();
11017
11018     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11019         gameMode == AnalyzeFile)
11020         TruncateGame();
11021
11022     ResurrectChessProgram();    /* in case it isn't running */
11023     gameMode = MachinePlaysBlack;
11024     pausing = FALSE;
11025     ModeHighlight();
11026     SetGameInfo();
11027     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11028     DisplayTitle(buf);
11029     if (first.sendName) {
11030       sprintf(buf, "name %s\n", gameInfo.white);
11031       SendToProgram(buf, &first);
11032     }
11033     if (first.sendTime) {
11034       if (first.useColors) {
11035         SendToProgram("white\n", &first); /*gnu kludge*/
11036       }
11037       SendTimeRemaining(&first, FALSE);
11038     }
11039     if (first.useColors) {
11040       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11041     }
11042     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11043     SetMachineThinkingEnables();
11044     first.maybeThinking = TRUE;
11045     StartClocks();
11046
11047     if (appData.autoFlipView && flipView) {
11048       flipView = !flipView;
11049       DrawPosition(FALSE, NULL);
11050       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11051     }
11052     if(bookHit) { // [HGM] book: simulate book reply
11053         static char bookMove[MSG_SIZ]; // a bit generous?
11054
11055         programStats.nodes = programStats.depth = programStats.time = 
11056         programStats.score = programStats.got_only_move = 0;
11057         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11058
11059         strcpy(bookMove, "move ");
11060         strcat(bookMove, bookHit);
11061         HandleMachineMove(bookMove, &first);
11062     }
11063 }
11064
11065
11066 void
11067 DisplayTwoMachinesTitle()
11068 {
11069     char buf[MSG_SIZ];
11070     if (appData.matchGames > 0) {
11071         if (first.twoMachinesColor[0] == 'w') {
11072             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11073                     gameInfo.white, gameInfo.black,
11074                     first.matchWins, second.matchWins,
11075                     matchGame - 1 - (first.matchWins + second.matchWins));
11076         } else {
11077             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11078                     gameInfo.white, gameInfo.black,
11079                     second.matchWins, first.matchWins,
11080                     matchGame - 1 - (first.matchWins + second.matchWins));
11081         }
11082     } else {
11083         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11084     }
11085     DisplayTitle(buf);
11086 }
11087
11088 void
11089 TwoMachinesEvent P((void))
11090 {
11091     int i;
11092     char buf[MSG_SIZ];
11093     ChessProgramState *onmove;
11094     char *bookHit = NULL;
11095     
11096     if (appData.noChessProgram) return;
11097
11098     switch (gameMode) {
11099       case TwoMachinesPlay:
11100         return;
11101       case MachinePlaysWhite:
11102       case MachinePlaysBlack:
11103         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11104             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11105             return;
11106         }
11107         /* fall through */
11108       case BeginningOfGame:
11109       case PlayFromGameFile:
11110       case EndOfGame:
11111         EditGameEvent();
11112         if (gameMode != EditGame) return;
11113         break;
11114       case EditPosition:
11115         EditPositionDone(TRUE);
11116         break;
11117       case AnalyzeMode:
11118       case AnalyzeFile:
11119         ExitAnalyzeMode();
11120         break;
11121       case EditGame:
11122       default:
11123         break;
11124     }
11125
11126 //    forwardMostMove = currentMove;
11127     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11128     ResurrectChessProgram();    /* in case first program isn't running */
11129
11130     if (second.pr == NULL) {
11131         StartChessProgram(&second);
11132         if (second.protocolVersion == 1) {
11133           TwoMachinesEventIfReady();
11134         } else {
11135           /* kludge: allow timeout for initial "feature" command */
11136           FreezeUI();
11137           DisplayMessage("", _("Starting second chess program"));
11138           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11139         }
11140         return;
11141     }
11142     DisplayMessage("", "");
11143     InitChessProgram(&second, FALSE);
11144     SendToProgram("force\n", &second);
11145     if (startedFromSetupPosition) {
11146         SendBoard(&second, backwardMostMove);
11147     if (appData.debugMode) {
11148         fprintf(debugFP, "Two Machines\n");
11149     }
11150     }
11151     for (i = backwardMostMove; i < forwardMostMove; i++) {
11152         SendMoveToProgram(i, &second);
11153     }
11154
11155     gameMode = TwoMachinesPlay;
11156     pausing = FALSE;
11157     ModeHighlight();
11158     SetGameInfo();
11159     DisplayTwoMachinesTitle();
11160     firstMove = TRUE;
11161     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11162         onmove = &first;
11163     } else {
11164         onmove = &second;
11165     }
11166
11167     SendToProgram(first.computerString, &first);
11168     if (first.sendName) {
11169       sprintf(buf, "name %s\n", second.tidy);
11170       SendToProgram(buf, &first);
11171     }
11172     SendToProgram(second.computerString, &second);
11173     if (second.sendName) {
11174       sprintf(buf, "name %s\n", first.tidy);
11175       SendToProgram(buf, &second);
11176     }
11177
11178     ResetClocks();
11179     if (!first.sendTime || !second.sendTime) {
11180         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11181         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11182     }
11183     if (onmove->sendTime) {
11184       if (onmove->useColors) {
11185         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11186       }
11187       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11188     }
11189     if (onmove->useColors) {
11190       SendToProgram(onmove->twoMachinesColor, onmove);
11191     }
11192     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11193 //    SendToProgram("go\n", onmove);
11194     onmove->maybeThinking = TRUE;
11195     SetMachineThinkingEnables();
11196
11197     StartClocks();
11198
11199     if(bookHit) { // [HGM] book: simulate book reply
11200         static char bookMove[MSG_SIZ]; // a bit generous?
11201
11202         programStats.nodes = programStats.depth = programStats.time = 
11203         programStats.score = programStats.got_only_move = 0;
11204         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11205
11206         strcpy(bookMove, "move ");
11207         strcat(bookMove, bookHit);
11208         savedMessage = bookMove; // args for deferred call
11209         savedState = onmove;
11210         ScheduleDelayedEvent(DeferredBookMove, 1);
11211     }
11212 }
11213
11214 void
11215 TrainingEvent()
11216 {
11217     if (gameMode == Training) {
11218       SetTrainingModeOff();
11219       gameMode = PlayFromGameFile;
11220       DisplayMessage("", _("Training mode off"));
11221     } else {
11222       gameMode = Training;
11223       animateTraining = appData.animate;
11224
11225       /* make sure we are not already at the end of the game */
11226       if (currentMove < forwardMostMove) {
11227         SetTrainingModeOn();
11228         DisplayMessage("", _("Training mode on"));
11229       } else {
11230         gameMode = PlayFromGameFile;
11231         DisplayError(_("Already at end of game"), 0);
11232       }
11233     }
11234     ModeHighlight();
11235 }
11236
11237 void
11238 IcsClientEvent()
11239 {
11240     if (!appData.icsActive) return;
11241     switch (gameMode) {
11242       case IcsPlayingWhite:
11243       case IcsPlayingBlack:
11244       case IcsObserving:
11245       case IcsIdle:
11246       case BeginningOfGame:
11247       case IcsExamining:
11248         return;
11249
11250       case EditGame:
11251         break;
11252
11253       case EditPosition:
11254         EditPositionDone(TRUE);
11255         break;
11256
11257       case AnalyzeMode:
11258       case AnalyzeFile:
11259         ExitAnalyzeMode();
11260         break;
11261         
11262       default:
11263         EditGameEvent();
11264         break;
11265     }
11266
11267     gameMode = IcsIdle;
11268     ModeHighlight();
11269     return;
11270 }
11271
11272
11273 void
11274 EditGameEvent()
11275 {
11276     int i;
11277
11278     switch (gameMode) {
11279       case Training:
11280         SetTrainingModeOff();
11281         break;
11282       case MachinePlaysWhite:
11283       case MachinePlaysBlack:
11284       case BeginningOfGame:
11285         SendToProgram("force\n", &first);
11286         SetUserThinkingEnables();
11287         break;
11288       case PlayFromGameFile:
11289         (void) StopLoadGameTimer();
11290         if (gameFileFP != NULL) {
11291             gameFileFP = NULL;
11292         }
11293         break;
11294       case EditPosition:
11295         EditPositionDone(TRUE);
11296         break;
11297       case AnalyzeMode:
11298       case AnalyzeFile:
11299         ExitAnalyzeMode();
11300         SendToProgram("force\n", &first);
11301         break;
11302       case TwoMachinesPlay:
11303         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11304         ResurrectChessProgram();
11305         SetUserThinkingEnables();
11306         break;
11307       case EndOfGame:
11308         ResurrectChessProgram();
11309         break;
11310       case IcsPlayingBlack:
11311       case IcsPlayingWhite:
11312         DisplayError(_("Warning: You are still playing a game"), 0);
11313         break;
11314       case IcsObserving:
11315         DisplayError(_("Warning: You are still observing a game"), 0);
11316         break;
11317       case IcsExamining:
11318         DisplayError(_("Warning: You are still examining a game"), 0);
11319         break;
11320       case IcsIdle:
11321         break;
11322       case EditGame:
11323       default:
11324         return;
11325     }
11326     
11327     pausing = FALSE;
11328     StopClocks();
11329     first.offeredDraw = second.offeredDraw = 0;
11330
11331     if (gameMode == PlayFromGameFile) {
11332         whiteTimeRemaining = timeRemaining[0][currentMove];
11333         blackTimeRemaining = timeRemaining[1][currentMove];
11334         DisplayTitle("");
11335     }
11336
11337     if (gameMode == MachinePlaysWhite ||
11338         gameMode == MachinePlaysBlack ||
11339         gameMode == TwoMachinesPlay ||
11340         gameMode == EndOfGame) {
11341         i = forwardMostMove;
11342         while (i > currentMove) {
11343             SendToProgram("undo\n", &first);
11344             i--;
11345         }
11346         whiteTimeRemaining = timeRemaining[0][currentMove];
11347         blackTimeRemaining = timeRemaining[1][currentMove];
11348         DisplayBothClocks();
11349         if (whiteFlag || blackFlag) {
11350             whiteFlag = blackFlag = 0;
11351         }
11352         DisplayTitle("");
11353     }           
11354     
11355     gameMode = EditGame;
11356     ModeHighlight();
11357     SetGameInfo();
11358 }
11359
11360
11361 void
11362 EditPositionEvent()
11363 {
11364     if (gameMode == EditPosition) {
11365         EditGameEvent();
11366         return;
11367     }
11368     
11369     EditGameEvent();
11370     if (gameMode != EditGame) return;
11371     
11372     gameMode = EditPosition;
11373     ModeHighlight();
11374     SetGameInfo();
11375     if (currentMove > 0)
11376       CopyBoard(boards[0], boards[currentMove]);
11377     
11378     blackPlaysFirst = !WhiteOnMove(currentMove);
11379     ResetClocks();
11380     currentMove = forwardMostMove = backwardMostMove = 0;
11381     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11382     DisplayMove(-1);
11383 }
11384
11385 void
11386 ExitAnalyzeMode()
11387 {
11388     /* [DM] icsEngineAnalyze - possible call from other functions */
11389     if (appData.icsEngineAnalyze) {
11390         appData.icsEngineAnalyze = FALSE;
11391
11392         DisplayMessage("",_("Close ICS engine analyze..."));
11393     }
11394     if (first.analysisSupport && first.analyzing) {
11395       SendToProgram("exit\n", &first);
11396       first.analyzing = FALSE;
11397     }
11398     thinkOutput[0] = NULLCHAR;
11399 }
11400
11401 void
11402 EditPositionDone(Boolean fakeRights)
11403 {
11404     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11405
11406     startedFromSetupPosition = TRUE;
11407     InitChessProgram(&first, FALSE);
11408     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11409       boards[0][EP_STATUS] = EP_NONE;
11410       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11411     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11412         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11413         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11414       } else boards[0][CASTLING][2] = NoRights;
11415     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11416         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11417         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11418       } else boards[0][CASTLING][5] = NoRights;
11419     }
11420     SendToProgram("force\n", &first);
11421     if (blackPlaysFirst) {
11422         strcpy(moveList[0], "");
11423         strcpy(parseList[0], "");
11424         currentMove = forwardMostMove = backwardMostMove = 1;
11425         CopyBoard(boards[1], boards[0]);
11426     } else {
11427         currentMove = forwardMostMove = backwardMostMove = 0;
11428     }
11429     SendBoard(&first, forwardMostMove);
11430     if (appData.debugMode) {
11431         fprintf(debugFP, "EditPosDone\n");
11432     }
11433     DisplayTitle("");
11434     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11435     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11436     gameMode = EditGame;
11437     ModeHighlight();
11438     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11439     ClearHighlights(); /* [AS] */
11440 }
11441
11442 /* Pause for `ms' milliseconds */
11443 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11444 void
11445 TimeDelay(ms)
11446      long ms;
11447 {
11448     TimeMark m1, m2;
11449
11450     GetTimeMark(&m1);
11451     do {
11452         GetTimeMark(&m2);
11453     } while (SubtractTimeMarks(&m2, &m1) < ms);
11454 }
11455
11456 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11457 void
11458 SendMultiLineToICS(buf)
11459      char *buf;
11460 {
11461     char temp[MSG_SIZ+1], *p;
11462     int len;
11463
11464     len = strlen(buf);
11465     if (len > MSG_SIZ)
11466       len = MSG_SIZ;
11467   
11468     strncpy(temp, buf, len);
11469     temp[len] = 0;
11470
11471     p = temp;
11472     while (*p) {
11473         if (*p == '\n' || *p == '\r')
11474           *p = ' ';
11475         ++p;
11476     }
11477
11478     strcat(temp, "\n");
11479     SendToICS(temp);
11480     SendToPlayer(temp, strlen(temp));
11481 }
11482
11483 void
11484 SetWhiteToPlayEvent()
11485 {
11486     if (gameMode == EditPosition) {
11487         blackPlaysFirst = FALSE;
11488         DisplayBothClocks();    /* works because currentMove is 0 */
11489     } else if (gameMode == IcsExamining) {
11490         SendToICS(ics_prefix);
11491         SendToICS("tomove white\n");
11492     }
11493 }
11494
11495 void
11496 SetBlackToPlayEvent()
11497 {
11498     if (gameMode == EditPosition) {
11499         blackPlaysFirst = TRUE;
11500         currentMove = 1;        /* kludge */
11501         DisplayBothClocks();
11502         currentMove = 0;
11503     } else if (gameMode == IcsExamining) {
11504         SendToICS(ics_prefix);
11505         SendToICS("tomove black\n");
11506     }
11507 }
11508
11509 void
11510 EditPositionMenuEvent(selection, x, y)
11511      ChessSquare selection;
11512      int x, y;
11513 {
11514     char buf[MSG_SIZ];
11515     ChessSquare piece = boards[0][y][x];
11516
11517     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11518
11519     switch (selection) {
11520       case ClearBoard:
11521         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11522             SendToICS(ics_prefix);
11523             SendToICS("bsetup clear\n");
11524         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11525             SendToICS(ics_prefix);
11526             SendToICS("clearboard\n");
11527         } else {
11528             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11529                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11530                 for (y = 0; y < BOARD_HEIGHT; y++) {
11531                     if (gameMode == IcsExamining) {
11532                         if (boards[currentMove][y][x] != EmptySquare) {
11533                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11534                                     AAA + x, ONE + y);
11535                             SendToICS(buf);
11536                         }
11537                     } else {
11538                         boards[0][y][x] = p;
11539                     }
11540                 }
11541             }
11542         }
11543         if (gameMode == EditPosition) {
11544             DrawPosition(FALSE, boards[0]);
11545         }
11546         break;
11547
11548       case WhitePlay:
11549         SetWhiteToPlayEvent();
11550         break;
11551
11552       case BlackPlay:
11553         SetBlackToPlayEvent();
11554         break;
11555
11556       case EmptySquare:
11557         if (gameMode == IcsExamining) {
11558             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11559             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11560             SendToICS(buf);
11561         } else {
11562             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11563                 if(x == BOARD_LEFT-2) {
11564                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11565                     boards[0][y][1] = 0;
11566                 } else
11567                 if(x == BOARD_RGHT+1) {
11568                     if(y >= gameInfo.holdingsSize) break;
11569                     boards[0][y][BOARD_WIDTH-2] = 0;
11570                 } else break;
11571             }
11572             boards[0][y][x] = EmptySquare;
11573             DrawPosition(FALSE, boards[0]);
11574         }
11575         break;
11576
11577       case PromotePiece:
11578         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11579            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11580             selection = (ChessSquare) (PROMOTED piece);
11581         } else if(piece == EmptySquare) selection = WhiteSilver;
11582         else selection = (ChessSquare)((int)piece - 1);
11583         goto defaultlabel;
11584
11585       case DemotePiece:
11586         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11587            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11588             selection = (ChessSquare) (DEMOTED piece);
11589         } else if(piece == EmptySquare) selection = BlackSilver;
11590         else selection = (ChessSquare)((int)piece + 1);       
11591         goto defaultlabel;
11592
11593       case WhiteQueen:
11594       case BlackQueen:
11595         if(gameInfo.variant == VariantShatranj ||
11596            gameInfo.variant == VariantXiangqi  ||
11597            gameInfo.variant == VariantCourier  ||
11598            gameInfo.variant == VariantMakruk     )
11599             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11600         goto defaultlabel;
11601
11602       case WhiteKing:
11603       case BlackKing:
11604         if(gameInfo.variant == VariantXiangqi)
11605             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11606         if(gameInfo.variant == VariantKnightmate)
11607             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11608       default:
11609         defaultlabel:
11610         if (gameMode == IcsExamining) {
11611             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11612             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11613                     PieceToChar(selection), AAA + x, ONE + y);
11614             SendToICS(buf);
11615         } else {
11616             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11617                 int n;
11618                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11619                     n = PieceToNumber(selection - BlackPawn);
11620                     if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11621                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11622                     boards[0][BOARD_HEIGHT-1-n][1]++;
11623                 } else
11624                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11625                     n = PieceToNumber(selection);
11626                     if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11627                     boards[0][n][BOARD_WIDTH-1] = selection;
11628                     boards[0][n][BOARD_WIDTH-2]++;
11629                 }
11630             } else
11631             boards[0][y][x] = selection;
11632             DrawPosition(TRUE, boards[0]);
11633         }
11634         break;
11635     }
11636 }
11637
11638
11639 void
11640 DropMenuEvent(selection, x, y)
11641      ChessSquare selection;
11642      int x, y;
11643 {
11644     ChessMove moveType;
11645
11646     switch (gameMode) {
11647       case IcsPlayingWhite:
11648       case MachinePlaysBlack:
11649         if (!WhiteOnMove(currentMove)) {
11650             DisplayMoveError(_("It is Black's turn"));
11651             return;
11652         }
11653         moveType = WhiteDrop;
11654         break;
11655       case IcsPlayingBlack:
11656       case MachinePlaysWhite:
11657         if (WhiteOnMove(currentMove)) {
11658             DisplayMoveError(_("It is White's turn"));
11659             return;
11660         }
11661         moveType = BlackDrop;
11662         break;
11663       case EditGame:
11664         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11665         break;
11666       default:
11667         return;
11668     }
11669
11670     if (moveType == BlackDrop && selection < BlackPawn) {
11671       selection = (ChessSquare) ((int) selection
11672                                  + (int) BlackPawn - (int) WhitePawn);
11673     }
11674     if (boards[currentMove][y][x] != EmptySquare) {
11675         DisplayMoveError(_("That square is occupied"));
11676         return;
11677     }
11678
11679     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11680 }
11681
11682 void
11683 AcceptEvent()
11684 {
11685     /* Accept a pending offer of any kind from opponent */
11686     
11687     if (appData.icsActive) {
11688         SendToICS(ics_prefix);
11689         SendToICS("accept\n");
11690     } else if (cmailMsgLoaded) {
11691         if (currentMove == cmailOldMove &&
11692             commentList[cmailOldMove] != NULL &&
11693             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11694                    "Black offers a draw" : "White offers a draw")) {
11695             TruncateGame();
11696             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11697             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11698         } else {
11699             DisplayError(_("There is no pending offer on this move"), 0);
11700             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11701         }
11702     } else {
11703         /* Not used for offers from chess program */
11704     }
11705 }
11706
11707 void
11708 DeclineEvent()
11709 {
11710     /* Decline a pending offer of any kind from opponent */
11711     
11712     if (appData.icsActive) {
11713         SendToICS(ics_prefix);
11714         SendToICS("decline\n");
11715     } else if (cmailMsgLoaded) {
11716         if (currentMove == cmailOldMove &&
11717             commentList[cmailOldMove] != NULL &&
11718             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11719                    "Black offers a draw" : "White offers a draw")) {
11720 #ifdef NOTDEF
11721             AppendComment(cmailOldMove, "Draw declined", TRUE);
11722             DisplayComment(cmailOldMove - 1, "Draw declined");
11723 #endif /*NOTDEF*/
11724         } else {
11725             DisplayError(_("There is no pending offer on this move"), 0);
11726         }
11727     } else {
11728         /* Not used for offers from chess program */
11729     }
11730 }
11731
11732 void
11733 RematchEvent()
11734 {
11735     /* Issue ICS rematch command */
11736     if (appData.icsActive) {
11737         SendToICS(ics_prefix);
11738         SendToICS("rematch\n");
11739     }
11740 }
11741
11742 void
11743 CallFlagEvent()
11744 {
11745     /* Call your opponent's flag (claim a win on time) */
11746     if (appData.icsActive) {
11747         SendToICS(ics_prefix);
11748         SendToICS("flag\n");
11749     } else {
11750         switch (gameMode) {
11751           default:
11752             return;
11753           case MachinePlaysWhite:
11754             if (whiteFlag) {
11755                 if (blackFlag)
11756                   GameEnds(GameIsDrawn, "Both players ran out of time",
11757                            GE_PLAYER);
11758                 else
11759                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11760             } else {
11761                 DisplayError(_("Your opponent is not out of time"), 0);
11762             }
11763             break;
11764           case MachinePlaysBlack:
11765             if (blackFlag) {
11766                 if (whiteFlag)
11767                   GameEnds(GameIsDrawn, "Both players ran out of time",
11768                            GE_PLAYER);
11769                 else
11770                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11771             } else {
11772                 DisplayError(_("Your opponent is not out of time"), 0);
11773             }
11774             break;
11775         }
11776     }
11777 }
11778
11779 void
11780 DrawEvent()
11781 {
11782     /* Offer draw or accept pending draw offer from opponent */
11783     
11784     if (appData.icsActive) {
11785         /* Note: tournament rules require draw offers to be
11786            made after you make your move but before you punch
11787            your clock.  Currently ICS doesn't let you do that;
11788            instead, you immediately punch your clock after making
11789            a move, but you can offer a draw at any time. */
11790         
11791         SendToICS(ics_prefix);
11792         SendToICS("draw\n");
11793     } else if (cmailMsgLoaded) {
11794         if (currentMove == cmailOldMove &&
11795             commentList[cmailOldMove] != NULL &&
11796             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11797                    "Black offers a draw" : "White offers a draw")) {
11798             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11799             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11800         } else if (currentMove == cmailOldMove + 1) {
11801             char *offer = WhiteOnMove(cmailOldMove) ?
11802               "White offers a draw" : "Black offers a draw";
11803             AppendComment(currentMove, offer, TRUE);
11804             DisplayComment(currentMove - 1, offer);
11805             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11806         } else {
11807             DisplayError(_("You must make your move before offering a draw"), 0);
11808             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11809         }
11810     } else if (first.offeredDraw) {
11811         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11812     } else {
11813         if (first.sendDrawOffers) {
11814             SendToProgram("draw\n", &first);
11815             userOfferedDraw = TRUE;
11816         }
11817     }
11818 }
11819
11820 void
11821 AdjournEvent()
11822 {
11823     /* Offer Adjourn or accept pending Adjourn offer from opponent */
11824     
11825     if (appData.icsActive) {
11826         SendToICS(ics_prefix);
11827         SendToICS("adjourn\n");
11828     } else {
11829         /* Currently GNU Chess doesn't offer or accept Adjourns */
11830     }
11831 }
11832
11833
11834 void
11835 AbortEvent()
11836 {
11837     /* Offer Abort or accept pending Abort offer from opponent */
11838     
11839     if (appData.icsActive) {
11840         SendToICS(ics_prefix);
11841         SendToICS("abort\n");
11842     } else {
11843         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11844     }
11845 }
11846
11847 void
11848 ResignEvent()
11849 {
11850     /* Resign.  You can do this even if it's not your turn. */
11851     
11852     if (appData.icsActive) {
11853         SendToICS(ics_prefix);
11854         SendToICS("resign\n");
11855     } else {
11856         switch (gameMode) {
11857           case MachinePlaysWhite:
11858             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11859             break;
11860           case MachinePlaysBlack:
11861             GameEnds(BlackWins, "White resigns", GE_PLAYER);
11862             break;
11863           case EditGame:
11864             if (cmailMsgLoaded) {
11865                 TruncateGame();
11866                 if (WhiteOnMove(cmailOldMove)) {
11867                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
11868                 } else {
11869                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11870                 }
11871                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11872             }
11873             break;
11874           default:
11875             break;
11876         }
11877     }
11878 }
11879
11880
11881 void
11882 StopObservingEvent()
11883 {
11884     /* Stop observing current games */
11885     SendToICS(ics_prefix);
11886     SendToICS("unobserve\n");
11887 }
11888
11889 void
11890 StopExaminingEvent()
11891 {
11892     /* Stop observing current game */
11893     SendToICS(ics_prefix);
11894     SendToICS("unexamine\n");
11895 }
11896
11897 void
11898 ForwardInner(target)
11899      int target;
11900 {
11901     int limit;
11902
11903     if (appData.debugMode)
11904         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11905                 target, currentMove, forwardMostMove);
11906
11907     if (gameMode == EditPosition)
11908       return;
11909
11910     if (gameMode == PlayFromGameFile && !pausing)
11911       PauseEvent();
11912     
11913     if (gameMode == IcsExamining && pausing)
11914       limit = pauseExamForwardMostMove;
11915     else
11916       limit = forwardMostMove;
11917     
11918     if (target > limit) target = limit;
11919
11920     if (target > 0 && moveList[target - 1][0]) {
11921         int fromX, fromY, toX, toY;
11922         toX = moveList[target - 1][2] - AAA;
11923         toY = moveList[target - 1][3] - ONE;
11924         if (moveList[target - 1][1] == '@') {
11925             if (appData.highlightLastMove) {
11926                 SetHighlights(-1, -1, toX, toY);
11927             }
11928         } else {
11929             fromX = moveList[target - 1][0] - AAA;
11930             fromY = moveList[target - 1][1] - ONE;
11931             if (target == currentMove + 1) {
11932                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11933             }
11934             if (appData.highlightLastMove) {
11935                 SetHighlights(fromX, fromY, toX, toY);
11936             }
11937         }
11938     }
11939     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11940         gameMode == Training || gameMode == PlayFromGameFile || 
11941         gameMode == AnalyzeFile) {
11942         while (currentMove < target) {
11943             SendMoveToProgram(currentMove++, &first);
11944         }
11945     } else {
11946         currentMove = target;
11947     }
11948     
11949     if (gameMode == EditGame || gameMode == EndOfGame) {
11950         whiteTimeRemaining = timeRemaining[0][currentMove];
11951         blackTimeRemaining = timeRemaining[1][currentMove];
11952     }
11953     DisplayBothClocks();
11954     DisplayMove(currentMove - 1);
11955     DrawPosition(FALSE, boards[currentMove]);
11956     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11957     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11958         DisplayComment(currentMove - 1, commentList[currentMove]);
11959     }
11960 }
11961
11962
11963 void
11964 ForwardEvent()
11965 {
11966     if (gameMode == IcsExamining && !pausing) {
11967         SendToICS(ics_prefix);
11968         SendToICS("forward\n");
11969     } else {
11970         ForwardInner(currentMove + 1);
11971     }
11972 }
11973
11974 void
11975 ToEndEvent()
11976 {
11977     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11978         /* to optimze, we temporarily turn off analysis mode while we feed
11979          * the remaining moves to the engine. Otherwise we get analysis output
11980          * after each move.
11981          */ 
11982         if (first.analysisSupport) {
11983           SendToProgram("exit\nforce\n", &first);
11984           first.analyzing = FALSE;
11985         }
11986     }
11987         
11988     if (gameMode == IcsExamining && !pausing) {
11989         SendToICS(ics_prefix);
11990         SendToICS("forward 999999\n");
11991     } else {
11992         ForwardInner(forwardMostMove);
11993     }
11994
11995     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11996         /* we have fed all the moves, so reactivate analysis mode */
11997         SendToProgram("analyze\n", &first);
11998         first.analyzing = TRUE;
11999         /*first.maybeThinking = TRUE;*/
12000         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12001     }
12002 }
12003
12004 void
12005 BackwardInner(target)
12006      int target;
12007 {
12008     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12009
12010     if (appData.debugMode)
12011         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12012                 target, currentMove, forwardMostMove);
12013
12014     if (gameMode == EditPosition) return;
12015     if (currentMove <= backwardMostMove) {
12016         ClearHighlights();
12017         DrawPosition(full_redraw, boards[currentMove]);
12018         return;
12019     }
12020     if (gameMode == PlayFromGameFile && !pausing)
12021       PauseEvent();
12022     
12023     if (moveList[target][0]) {
12024         int fromX, fromY, toX, toY;
12025         toX = moveList[target][2] - AAA;
12026         toY = moveList[target][3] - ONE;
12027         if (moveList[target][1] == '@') {
12028             if (appData.highlightLastMove) {
12029                 SetHighlights(-1, -1, toX, toY);
12030             }
12031         } else {
12032             fromX = moveList[target][0] - AAA;
12033             fromY = moveList[target][1] - ONE;
12034             if (target == currentMove - 1) {
12035                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12036             }
12037             if (appData.highlightLastMove) {
12038                 SetHighlights(fromX, fromY, toX, toY);
12039             }
12040         }
12041     }
12042     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12043         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12044         while (currentMove > target) {
12045             SendToProgram("undo\n", &first);
12046             currentMove--;
12047         }
12048     } else {
12049         currentMove = target;
12050     }
12051     
12052     if (gameMode == EditGame || gameMode == EndOfGame) {
12053         whiteTimeRemaining = timeRemaining[0][currentMove];
12054         blackTimeRemaining = timeRemaining[1][currentMove];
12055     }
12056     DisplayBothClocks();
12057     DisplayMove(currentMove - 1);
12058     DrawPosition(full_redraw, boards[currentMove]);
12059     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12060     // [HGM] PV info: routine tests if comment empty
12061     DisplayComment(currentMove - 1, commentList[currentMove]);
12062 }
12063
12064 void
12065 BackwardEvent()
12066 {
12067     if (gameMode == IcsExamining && !pausing) {
12068         SendToICS(ics_prefix);
12069         SendToICS("backward\n");
12070     } else {
12071         BackwardInner(currentMove - 1);
12072     }
12073 }
12074
12075 void
12076 ToStartEvent()
12077 {
12078     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12079         /* to optimize, we temporarily turn off analysis mode while we undo
12080          * all the moves. Otherwise we get analysis output after each undo.
12081          */ 
12082         if (first.analysisSupport) {
12083           SendToProgram("exit\nforce\n", &first);
12084           first.analyzing = FALSE;
12085         }
12086     }
12087
12088     if (gameMode == IcsExamining && !pausing) {
12089         SendToICS(ics_prefix);
12090         SendToICS("backward 999999\n");
12091     } else {
12092         BackwardInner(backwardMostMove);
12093     }
12094
12095     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12096         /* we have fed all the moves, so reactivate analysis mode */
12097         SendToProgram("analyze\n", &first);
12098         first.analyzing = TRUE;
12099         /*first.maybeThinking = TRUE;*/
12100         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12101     }
12102 }
12103
12104 void
12105 ToNrEvent(int to)
12106 {
12107   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12108   if (to >= forwardMostMove) to = forwardMostMove;
12109   if (to <= backwardMostMove) to = backwardMostMove;
12110   if (to < currentMove) {
12111     BackwardInner(to);
12112   } else {
12113     ForwardInner(to);
12114   }
12115 }
12116
12117 void
12118 RevertEvent()
12119 {
12120     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12121         return;
12122     }
12123     if (gameMode != IcsExamining) {
12124         DisplayError(_("You are not examining a game"), 0);
12125         return;
12126     }
12127     if (pausing) {
12128         DisplayError(_("You can't revert while pausing"), 0);
12129         return;
12130     }
12131     SendToICS(ics_prefix);
12132     SendToICS("revert\n");
12133 }
12134
12135 void
12136 RetractMoveEvent()
12137 {
12138     switch (gameMode) {
12139       case MachinePlaysWhite:
12140       case MachinePlaysBlack:
12141         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12142             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12143             return;
12144         }
12145         if (forwardMostMove < 2) return;
12146         currentMove = forwardMostMove = forwardMostMove - 2;
12147         whiteTimeRemaining = timeRemaining[0][currentMove];
12148         blackTimeRemaining = timeRemaining[1][currentMove];
12149         DisplayBothClocks();
12150         DisplayMove(currentMove - 1);
12151         ClearHighlights();/*!! could figure this out*/
12152         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12153         SendToProgram("remove\n", &first);
12154         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12155         break;
12156
12157       case BeginningOfGame:
12158       default:
12159         break;
12160
12161       case IcsPlayingWhite:
12162       case IcsPlayingBlack:
12163         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12164             SendToICS(ics_prefix);
12165             SendToICS("takeback 2\n");
12166         } else {
12167             SendToICS(ics_prefix);
12168             SendToICS("takeback 1\n");
12169         }
12170         break;
12171     }
12172 }
12173
12174 void
12175 MoveNowEvent()
12176 {
12177     ChessProgramState *cps;
12178
12179     switch (gameMode) {
12180       case MachinePlaysWhite:
12181         if (!WhiteOnMove(forwardMostMove)) {
12182             DisplayError(_("It is your turn"), 0);
12183             return;
12184         }
12185         cps = &first;
12186         break;
12187       case MachinePlaysBlack:
12188         if (WhiteOnMove(forwardMostMove)) {
12189             DisplayError(_("It is your turn"), 0);
12190             return;
12191         }
12192         cps = &first;
12193         break;
12194       case TwoMachinesPlay:
12195         if (WhiteOnMove(forwardMostMove) ==
12196             (first.twoMachinesColor[0] == 'w')) {
12197             cps = &first;
12198         } else {
12199             cps = &second;
12200         }
12201         break;
12202       case BeginningOfGame:
12203       default:
12204         return;
12205     }
12206     SendToProgram("?\n", cps);
12207 }
12208
12209 void
12210 TruncateGameEvent()
12211 {
12212     EditGameEvent();
12213     if (gameMode != EditGame) return;
12214     TruncateGame();
12215 }
12216
12217 void
12218 TruncateGame()
12219 {
12220     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12221     if (forwardMostMove > currentMove) {
12222         if (gameInfo.resultDetails != NULL) {
12223             free(gameInfo.resultDetails);
12224             gameInfo.resultDetails = NULL;
12225             gameInfo.result = GameUnfinished;
12226         }
12227         forwardMostMove = currentMove;
12228         HistorySet(parseList, backwardMostMove, forwardMostMove,
12229                    currentMove-1);
12230     }
12231 }
12232
12233 void
12234 HintEvent()
12235 {
12236     if (appData.noChessProgram) return;
12237     switch (gameMode) {
12238       case MachinePlaysWhite:
12239         if (WhiteOnMove(forwardMostMove)) {
12240             DisplayError(_("Wait until your turn"), 0);
12241             return;
12242         }
12243         break;
12244       case BeginningOfGame:
12245       case MachinePlaysBlack:
12246         if (!WhiteOnMove(forwardMostMove)) {
12247             DisplayError(_("Wait until your turn"), 0);
12248             return;
12249         }
12250         break;
12251       default:
12252         DisplayError(_("No hint available"), 0);
12253         return;
12254     }
12255     SendToProgram("hint\n", &first);
12256     hintRequested = TRUE;
12257 }
12258
12259 void
12260 BookEvent()
12261 {
12262     if (appData.noChessProgram) return;
12263     switch (gameMode) {
12264       case MachinePlaysWhite:
12265         if (WhiteOnMove(forwardMostMove)) {
12266             DisplayError(_("Wait until your turn"), 0);
12267             return;
12268         }
12269         break;
12270       case BeginningOfGame:
12271       case MachinePlaysBlack:
12272         if (!WhiteOnMove(forwardMostMove)) {
12273             DisplayError(_("Wait until your turn"), 0);
12274             return;
12275         }
12276         break;
12277       case EditPosition:
12278         EditPositionDone(TRUE);
12279         break;
12280       case TwoMachinesPlay:
12281         return;
12282       default:
12283         break;
12284     }
12285     SendToProgram("bk\n", &first);
12286     bookOutput[0] = NULLCHAR;
12287     bookRequested = TRUE;
12288 }
12289
12290 void
12291 AboutGameEvent()
12292 {
12293     char *tags = PGNTags(&gameInfo);
12294     TagsPopUp(tags, CmailMsg());
12295     free(tags);
12296 }
12297
12298 /* end button procedures */
12299
12300 void
12301 PrintPosition(fp, move)
12302      FILE *fp;
12303      int move;
12304 {
12305     int i, j;
12306     
12307     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12308         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12309             char c = PieceToChar(boards[move][i][j]);
12310             fputc(c == 'x' ? '.' : c, fp);
12311             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12312         }
12313     }
12314     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12315       fprintf(fp, "white to play\n");
12316     else
12317       fprintf(fp, "black to play\n");
12318 }
12319
12320 void
12321 PrintOpponents(fp)
12322      FILE *fp;
12323 {
12324     if (gameInfo.white != NULL) {
12325         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12326     } else {
12327         fprintf(fp, "\n");
12328     }
12329 }
12330
12331 /* Find last component of program's own name, using some heuristics */
12332 void
12333 TidyProgramName(prog, host, buf)
12334      char *prog, *host, buf[MSG_SIZ];
12335 {
12336     char *p, *q;
12337     int local = (strcmp(host, "localhost") == 0);
12338     while (!local && (p = strchr(prog, ';')) != NULL) {
12339         p++;
12340         while (*p == ' ') p++;
12341         prog = p;
12342     }
12343     if (*prog == '"' || *prog == '\'') {
12344         q = strchr(prog + 1, *prog);
12345     } else {
12346         q = strchr(prog, ' ');
12347     }
12348     if (q == NULL) q = prog + strlen(prog);
12349     p = q;
12350     while (p >= prog && *p != '/' && *p != '\\') p--;
12351     p++;
12352     if(p == prog && *p == '"') p++;
12353     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12354     memcpy(buf, p, q - p);
12355     buf[q - p] = NULLCHAR;
12356     if (!local) {
12357         strcat(buf, "@");
12358         strcat(buf, host);
12359     }
12360 }
12361
12362 char *
12363 TimeControlTagValue()
12364 {
12365     char buf[MSG_SIZ];
12366     if (!appData.clockMode) {
12367         strcpy(buf, "-");
12368     } else if (movesPerSession > 0) {
12369         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12370     } else if (timeIncrement == 0) {
12371         sprintf(buf, "%ld", timeControl/1000);
12372     } else {
12373         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12374     }
12375     return StrSave(buf);
12376 }
12377
12378 void
12379 SetGameInfo()
12380 {
12381     /* This routine is used only for certain modes */
12382     VariantClass v = gameInfo.variant;
12383     ChessMove r = GameUnfinished;
12384     char *p = NULL;
12385
12386     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12387         r = gameInfo.result; 
12388         p = gameInfo.resultDetails; 
12389         gameInfo.resultDetails = NULL;
12390     }
12391     ClearGameInfo(&gameInfo);
12392     gameInfo.variant = v;
12393
12394     switch (gameMode) {
12395       case MachinePlaysWhite:
12396         gameInfo.event = StrSave( appData.pgnEventHeader );
12397         gameInfo.site = StrSave(HostName());
12398         gameInfo.date = PGNDate();
12399         gameInfo.round = StrSave("-");
12400         gameInfo.white = StrSave(first.tidy);
12401         gameInfo.black = StrSave(UserName());
12402         gameInfo.timeControl = TimeControlTagValue();
12403         break;
12404
12405       case MachinePlaysBlack:
12406         gameInfo.event = StrSave( appData.pgnEventHeader );
12407         gameInfo.site = StrSave(HostName());
12408         gameInfo.date = PGNDate();
12409         gameInfo.round = StrSave("-");
12410         gameInfo.white = StrSave(UserName());
12411         gameInfo.black = StrSave(first.tidy);
12412         gameInfo.timeControl = TimeControlTagValue();
12413         break;
12414
12415       case TwoMachinesPlay:
12416         gameInfo.event = StrSave( appData.pgnEventHeader );
12417         gameInfo.site = StrSave(HostName());
12418         gameInfo.date = PGNDate();
12419         if (matchGame > 0) {
12420             char buf[MSG_SIZ];
12421             sprintf(buf, "%d", matchGame);
12422             gameInfo.round = StrSave(buf);
12423         } else {
12424             gameInfo.round = StrSave("-");
12425         }
12426         if (first.twoMachinesColor[0] == 'w') {
12427             gameInfo.white = StrSave(first.tidy);
12428             gameInfo.black = StrSave(second.tidy);
12429         } else {
12430             gameInfo.white = StrSave(second.tidy);
12431             gameInfo.black = StrSave(first.tidy);
12432         }
12433         gameInfo.timeControl = TimeControlTagValue();
12434         break;
12435
12436       case EditGame:
12437         gameInfo.event = StrSave("Edited game");
12438         gameInfo.site = StrSave(HostName());
12439         gameInfo.date = PGNDate();
12440         gameInfo.round = StrSave("-");
12441         gameInfo.white = StrSave("-");
12442         gameInfo.black = StrSave("-");
12443         gameInfo.result = r;
12444         gameInfo.resultDetails = p;
12445         break;
12446
12447       case EditPosition:
12448         gameInfo.event = StrSave("Edited position");
12449         gameInfo.site = StrSave(HostName());
12450         gameInfo.date = PGNDate();
12451         gameInfo.round = StrSave("-");
12452         gameInfo.white = StrSave("-");
12453         gameInfo.black = StrSave("-");
12454         break;
12455
12456       case IcsPlayingWhite:
12457       case IcsPlayingBlack:
12458       case IcsObserving:
12459       case IcsExamining:
12460         break;
12461
12462       case PlayFromGameFile:
12463         gameInfo.event = StrSave("Game from non-PGN file");
12464         gameInfo.site = StrSave(HostName());
12465         gameInfo.date = PGNDate();
12466         gameInfo.round = StrSave("-");
12467         gameInfo.white = StrSave("?");
12468         gameInfo.black = StrSave("?");
12469         break;
12470
12471       default:
12472         break;
12473     }
12474 }
12475
12476 void
12477 ReplaceComment(index, text)
12478      int index;
12479      char *text;
12480 {
12481     int len;
12482
12483     while (*text == '\n') text++;
12484     len = strlen(text);
12485     while (len > 0 && text[len - 1] == '\n') len--;
12486
12487     if (commentList[index] != NULL)
12488       free(commentList[index]);
12489
12490     if (len == 0) {
12491         commentList[index] = NULL;
12492         return;
12493     }
12494   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12495       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12496       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12497     commentList[index] = (char *) malloc(len + 2);
12498     strncpy(commentList[index], text, len);
12499     commentList[index][len] = '\n';
12500     commentList[index][len + 1] = NULLCHAR;
12501   } else { 
12502     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12503     char *p;
12504     commentList[index] = (char *) malloc(len + 6);
12505     strcpy(commentList[index], "{\n");
12506     strncpy(commentList[index]+2, text, len);
12507     commentList[index][len+2] = NULLCHAR;
12508     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12509     strcat(commentList[index], "\n}\n");
12510   }
12511 }
12512
12513 void
12514 CrushCRs(text)
12515      char *text;
12516 {
12517   char *p = text;
12518   char *q = text;
12519   char ch;
12520
12521   do {
12522     ch = *p++;
12523     if (ch == '\r') continue;
12524     *q++ = ch;
12525   } while (ch != '\0');
12526 }
12527
12528 void
12529 AppendComment(index, text, addBraces)
12530      int index;
12531      char *text;
12532      Boolean addBraces; // [HGM] braces: tells if we should add {}
12533 {
12534     int oldlen, len;
12535     char *old;
12536
12537 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12538     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12539
12540     CrushCRs(text);
12541     while (*text == '\n') text++;
12542     len = strlen(text);
12543     while (len > 0 && text[len - 1] == '\n') len--;
12544
12545     if (len == 0) return;
12546
12547     if (commentList[index] != NULL) {
12548         old = commentList[index];
12549         oldlen = strlen(old);
12550         while(commentList[index][oldlen-1] ==  '\n')
12551           commentList[index][--oldlen] = NULLCHAR;
12552         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12553         strcpy(commentList[index], old);
12554         free(old);
12555         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12556         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12557           if(addBraces) addBraces = FALSE; else { text++; len--; }
12558           while (*text == '\n') { text++; len--; }
12559           commentList[index][--oldlen] = NULLCHAR;
12560       }
12561         if(addBraces) strcat(commentList[index], "\n{\n");
12562         else          strcat(commentList[index], "\n");
12563         strcat(commentList[index], text);
12564         if(addBraces) strcat(commentList[index], "\n}\n");
12565         else          strcat(commentList[index], "\n");
12566     } else {
12567         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12568         if(addBraces)
12569              strcpy(commentList[index], "{\n");
12570         else commentList[index][0] = NULLCHAR;
12571         strcat(commentList[index], text);
12572         strcat(commentList[index], "\n");
12573         if(addBraces) strcat(commentList[index], "}\n");
12574     }
12575 }
12576
12577 static char * FindStr( char * text, char * sub_text )
12578 {
12579     char * result = strstr( text, sub_text );
12580
12581     if( result != NULL ) {
12582         result += strlen( sub_text );
12583     }
12584
12585     return result;
12586 }
12587
12588 /* [AS] Try to extract PV info from PGN comment */
12589 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12590 char *GetInfoFromComment( int index, char * text )
12591 {
12592     char * sep = text;
12593
12594     if( text != NULL && index > 0 ) {
12595         int score = 0;
12596         int depth = 0;
12597         int time = -1, sec = 0, deci;
12598         char * s_eval = FindStr( text, "[%eval " );
12599         char * s_emt = FindStr( text, "[%emt " );
12600
12601         if( s_eval != NULL || s_emt != NULL ) {
12602             /* New style */
12603             char delim;
12604
12605             if( s_eval != NULL ) {
12606                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12607                     return text;
12608                 }
12609
12610                 if( delim != ']' ) {
12611                     return text;
12612                 }
12613             }
12614
12615             if( s_emt != NULL ) {
12616             }
12617                 return text;
12618         }
12619         else {
12620             /* We expect something like: [+|-]nnn.nn/dd */
12621             int score_lo = 0;
12622
12623             if(*text != '{') return text; // [HGM] braces: must be normal comment
12624
12625             sep = strchr( text, '/' );
12626             if( sep == NULL || sep < (text+4) ) {
12627                 return text;
12628             }
12629
12630             time = -1; sec = -1; deci = -1;
12631             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12632                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12633                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12634                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12635                 return text;
12636             }
12637
12638             if( score_lo < 0 || score_lo >= 100 ) {
12639                 return text;
12640             }
12641
12642             if(sec >= 0) time = 600*time + 10*sec; else
12643             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12644
12645             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12646
12647             /* [HGM] PV time: now locate end of PV info */
12648             while( *++sep >= '0' && *sep <= '9'); // strip depth
12649             if(time >= 0)
12650             while( *++sep >= '0' && *sep <= '9'); // strip time
12651             if(sec >= 0)
12652             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12653             if(deci >= 0)
12654             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12655             while(*sep == ' ') sep++;
12656         }
12657
12658         if( depth <= 0 ) {
12659             return text;
12660         }
12661
12662         if( time < 0 ) {
12663             time = -1;
12664         }
12665
12666         pvInfoList[index-1].depth = depth;
12667         pvInfoList[index-1].score = score;
12668         pvInfoList[index-1].time  = 10*time; // centi-sec
12669         if(*sep == '}') *sep = 0; else *--sep = '{';
12670     }
12671     return sep;
12672 }
12673
12674 void
12675 SendToProgram(message, cps)
12676      char *message;
12677      ChessProgramState *cps;
12678 {
12679     int count, outCount, error;
12680     char buf[MSG_SIZ];
12681
12682     if (cps->pr == NULL) return;
12683     Attention(cps);
12684     
12685     if (appData.debugMode) {
12686         TimeMark now;
12687         GetTimeMark(&now);
12688         fprintf(debugFP, "%ld >%-6s: %s", 
12689                 SubtractTimeMarks(&now, &programStartTime),
12690                 cps->which, message);
12691     }
12692     
12693     count = strlen(message);
12694     outCount = OutputToProcess(cps->pr, message, count, &error);
12695     if (outCount < count && !exiting 
12696                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12697         sprintf(buf, _("Error writing to %s chess program"), cps->which);
12698         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12699             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12700                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12701                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12702             } else {
12703                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12704             }
12705             gameInfo.resultDetails = StrSave(buf);
12706         }
12707         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12708     }
12709 }
12710
12711 void
12712 ReceiveFromProgram(isr, closure, message, count, error)
12713      InputSourceRef isr;
12714      VOIDSTAR closure;
12715      char *message;
12716      int count;
12717      int error;
12718 {
12719     char *end_str;
12720     char buf[MSG_SIZ];
12721     ChessProgramState *cps = (ChessProgramState *)closure;
12722
12723     if (isr != cps->isr) return; /* Killed intentionally */
12724     if (count <= 0) {
12725         if (count == 0) {
12726             sprintf(buf,
12727                     _("Error: %s chess program (%s) exited unexpectedly"),
12728                     cps->which, cps->program);
12729         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12730                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12731                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12732                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12733                 } else {
12734                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12735                 }
12736                 gameInfo.resultDetails = StrSave(buf);
12737             }
12738             RemoveInputSource(cps->isr);
12739             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12740         } else {
12741             sprintf(buf,
12742                     _("Error reading from %s chess program (%s)"),
12743                     cps->which, cps->program);
12744             RemoveInputSource(cps->isr);
12745
12746             /* [AS] Program is misbehaving badly... kill it */
12747             if( count == -2 ) {
12748                 DestroyChildProcess( cps->pr, 9 );
12749                 cps->pr = NoProc;
12750             }
12751
12752             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12753         }
12754         return;
12755     }
12756     
12757     if ((end_str = strchr(message, '\r')) != NULL)
12758       *end_str = NULLCHAR;
12759     if ((end_str = strchr(message, '\n')) != NULL)
12760       *end_str = NULLCHAR;
12761     
12762     if (appData.debugMode) {
12763         TimeMark now; int print = 1;
12764         char *quote = ""; char c; int i;
12765
12766         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12767                 char start = message[0];
12768                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12769                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
12770                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
12771                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12772                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12773                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
12774                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12775                    sscanf(message, "pong %c", &c)!=1   && start != '#')
12776                         { quote = "# "; print = (appData.engineComments == 2); }
12777                 message[0] = start; // restore original message
12778         }
12779         if(print) {
12780                 GetTimeMark(&now);
12781                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
12782                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
12783                         quote,
12784                         message);
12785         }
12786     }
12787
12788     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12789     if (appData.icsEngineAnalyze) {
12790         if (strstr(message, "whisper") != NULL ||
12791              strstr(message, "kibitz") != NULL || 
12792             strstr(message, "tellics") != NULL) return;
12793     }
12794
12795     HandleMachineMove(message, cps);
12796 }
12797
12798
12799 void
12800 SendTimeControl(cps, mps, tc, inc, sd, st)
12801      ChessProgramState *cps;
12802      int mps, inc, sd, st;
12803      long tc;
12804 {
12805     char buf[MSG_SIZ];
12806     int seconds;
12807
12808     if( timeControl_2 > 0 ) {
12809         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12810             tc = timeControl_2;
12811         }
12812     }
12813     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12814     inc /= cps->timeOdds;
12815     st  /= cps->timeOdds;
12816
12817     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12818
12819     if (st > 0) {
12820       /* Set exact time per move, normally using st command */
12821       if (cps->stKludge) {
12822         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12823         seconds = st % 60;
12824         if (seconds == 0) {
12825           sprintf(buf, "level 1 %d\n", st/60);
12826         } else {
12827           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12828         }
12829       } else {
12830         sprintf(buf, "st %d\n", st);
12831       }
12832     } else {
12833       /* Set conventional or incremental time control, using level command */
12834       if (seconds == 0) {
12835         /* Note old gnuchess bug -- minutes:seconds used to not work.
12836            Fixed in later versions, but still avoid :seconds
12837            when seconds is 0. */
12838         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12839       } else {
12840         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12841                 seconds, inc/1000);
12842       }
12843     }
12844     SendToProgram(buf, cps);
12845
12846     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12847     /* Orthogonally, limit search to given depth */
12848     if (sd > 0) {
12849       if (cps->sdKludge) {
12850         sprintf(buf, "depth\n%d\n", sd);
12851       } else {
12852         sprintf(buf, "sd %d\n", sd);
12853       }
12854       SendToProgram(buf, cps);
12855     }
12856
12857     if(cps->nps > 0) { /* [HGM] nps */
12858         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12859         else {
12860                 sprintf(buf, "nps %d\n", cps->nps);
12861               SendToProgram(buf, cps);
12862         }
12863     }
12864 }
12865
12866 ChessProgramState *WhitePlayer()
12867 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12868 {
12869     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
12870        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12871         return &second;
12872     return &first;
12873 }
12874
12875 void
12876 SendTimeRemaining(cps, machineWhite)
12877      ChessProgramState *cps;
12878      int /*boolean*/ machineWhite;
12879 {
12880     char message[MSG_SIZ];
12881     long time, otime;
12882
12883     /* Note: this routine must be called when the clocks are stopped
12884        or when they have *just* been set or switched; otherwise
12885        it will be off by the time since the current tick started.
12886     */
12887     if (machineWhite) {
12888         time = whiteTimeRemaining / 10;
12889         otime = blackTimeRemaining / 10;
12890     } else {
12891         time = blackTimeRemaining / 10;
12892         otime = whiteTimeRemaining / 10;
12893     }
12894     /* [HGM] translate opponent's time by time-odds factor */
12895     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12896     if (appData.debugMode) {
12897         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12898     }
12899
12900     if (time <= 0) time = 1;
12901     if (otime <= 0) otime = 1;
12902     
12903     sprintf(message, "time %ld\n", time);
12904     SendToProgram(message, cps);
12905
12906     sprintf(message, "otim %ld\n", otime);
12907     SendToProgram(message, cps);
12908 }
12909
12910 int
12911 BoolFeature(p, name, loc, cps)
12912      char **p;
12913      char *name;
12914      int *loc;
12915      ChessProgramState *cps;
12916 {
12917   char buf[MSG_SIZ];
12918   int len = strlen(name);
12919   int val;
12920   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12921     (*p) += len + 1;
12922     sscanf(*p, "%d", &val);
12923     *loc = (val != 0);
12924     while (**p && **p != ' ') (*p)++;
12925     sprintf(buf, "accepted %s\n", name);
12926     SendToProgram(buf, cps);
12927     return TRUE;
12928   }
12929   return FALSE;
12930 }
12931
12932 int
12933 IntFeature(p, name, loc, cps)
12934      char **p;
12935      char *name;
12936      int *loc;
12937      ChessProgramState *cps;
12938 {
12939   char buf[MSG_SIZ];
12940   int len = strlen(name);
12941   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12942     (*p) += len + 1;
12943     sscanf(*p, "%d", loc);
12944     while (**p && **p != ' ') (*p)++;
12945     sprintf(buf, "accepted %s\n", name);
12946     SendToProgram(buf, cps);
12947     return TRUE;
12948   }
12949   return FALSE;
12950 }
12951
12952 int
12953 StringFeature(p, name, loc, cps)
12954      char **p;
12955      char *name;
12956      char loc[];
12957      ChessProgramState *cps;
12958 {
12959   char buf[MSG_SIZ];
12960   int len = strlen(name);
12961   if (strncmp((*p), name, len) == 0
12962       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12963     (*p) += len + 2;
12964     sscanf(*p, "%[^\"]", loc);
12965     while (**p && **p != '\"') (*p)++;
12966     if (**p == '\"') (*p)++;
12967     sprintf(buf, "accepted %s\n", name);
12968     SendToProgram(buf, cps);
12969     return TRUE;
12970   }
12971   return FALSE;
12972 }
12973
12974 int 
12975 ParseOption(Option *opt, ChessProgramState *cps)
12976 // [HGM] options: process the string that defines an engine option, and determine
12977 // name, type, default value, and allowed value range
12978 {
12979         char *p, *q, buf[MSG_SIZ];
12980         int n, min = (-1)<<31, max = 1<<31, def;
12981
12982         if(p = strstr(opt->name, " -spin ")) {
12983             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12984             if(max < min) max = min; // enforce consistency
12985             if(def < min) def = min;
12986             if(def > max) def = max;
12987             opt->value = def;
12988             opt->min = min;
12989             opt->max = max;
12990             opt->type = Spin;
12991         } else if((p = strstr(opt->name, " -slider "))) {
12992             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12993             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12994             if(max < min) max = min; // enforce consistency
12995             if(def < min) def = min;
12996             if(def > max) def = max;
12997             opt->value = def;
12998             opt->min = min;
12999             opt->max = max;
13000             opt->type = Spin; // Slider;
13001         } else if((p = strstr(opt->name, " -string "))) {
13002             opt->textValue = p+9;
13003             opt->type = TextBox;
13004         } else if((p = strstr(opt->name, " -file "))) {
13005             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13006             opt->textValue = p+7;
13007             opt->type = TextBox; // FileName;
13008         } else if((p = strstr(opt->name, " -path "))) {
13009             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13010             opt->textValue = p+7;
13011             opt->type = TextBox; // PathName;
13012         } else if(p = strstr(opt->name, " -check ")) {
13013             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13014             opt->value = (def != 0);
13015             opt->type = CheckBox;
13016         } else if(p = strstr(opt->name, " -combo ")) {
13017             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13018             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13019             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13020             opt->value = n = 0;
13021             while(q = StrStr(q, " /// ")) {
13022                 n++; *q = 0;    // count choices, and null-terminate each of them
13023                 q += 5;
13024                 if(*q == '*') { // remember default, which is marked with * prefix
13025                     q++;
13026                     opt->value = n;
13027                 }
13028                 cps->comboList[cps->comboCnt++] = q;
13029             }
13030             cps->comboList[cps->comboCnt++] = NULL;
13031             opt->max = n + 1;
13032             opt->type = ComboBox;
13033         } else if(p = strstr(opt->name, " -button")) {
13034             opt->type = Button;
13035         } else if(p = strstr(opt->name, " -save")) {
13036             opt->type = SaveButton;
13037         } else return FALSE;
13038         *p = 0; // terminate option name
13039         // now look if the command-line options define a setting for this engine option.
13040         if(cps->optionSettings && cps->optionSettings[0])
13041             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13042         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13043                 sprintf(buf, "option %s", p);
13044                 if(p = strstr(buf, ",")) *p = 0;
13045                 strcat(buf, "\n");
13046                 SendToProgram(buf, cps);
13047         }
13048         return TRUE;
13049 }
13050
13051 void
13052 FeatureDone(cps, val)
13053      ChessProgramState* cps;
13054      int val;
13055 {
13056   DelayedEventCallback cb = GetDelayedEvent();
13057   if ((cb == InitBackEnd3 && cps == &first) ||
13058       (cb == TwoMachinesEventIfReady && cps == &second)) {
13059     CancelDelayedEvent();
13060     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13061   }
13062   cps->initDone = val;
13063 }
13064
13065 /* Parse feature command from engine */
13066 void
13067 ParseFeatures(args, cps)
13068      char* args;
13069      ChessProgramState *cps;  
13070 {
13071   char *p = args;
13072   char *q;
13073   int val;
13074   char buf[MSG_SIZ];
13075
13076   for (;;) {
13077     while (*p == ' ') p++;
13078     if (*p == NULLCHAR) return;
13079
13080     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13081     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13082     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13083     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13084     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13085     if (BoolFeature(&p, "reuse", &val, cps)) {
13086       /* Engine can disable reuse, but can't enable it if user said no */
13087       if (!val) cps->reuse = FALSE;
13088       continue;
13089     }
13090     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13091     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13092       if (gameMode == TwoMachinesPlay) {
13093         DisplayTwoMachinesTitle();
13094       } else {
13095         DisplayTitle("");
13096       }
13097       continue;
13098     }
13099     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13100     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13101     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13102     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13103     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13104     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13105     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13106     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13107     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13108     if (IntFeature(&p, "done", &val, cps)) {
13109       FeatureDone(cps, val);
13110       continue;
13111     }
13112     /* Added by Tord: */
13113     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13114     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13115     /* End of additions by Tord */
13116
13117     /* [HGM] added features: */
13118     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13119     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13120     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13121     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13122     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13123     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13124     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13125         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13126             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13127             SendToProgram(buf, cps);
13128             continue;
13129         }
13130         if(cps->nrOptions >= MAX_OPTIONS) {
13131             cps->nrOptions--;
13132             sprintf(buf, "%s engine has too many options\n", cps->which);
13133             DisplayError(buf, 0);
13134         }
13135         continue;
13136     }
13137     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13138     /* End of additions by HGM */
13139
13140     /* unknown feature: complain and skip */
13141     q = p;
13142     while (*q && *q != '=') q++;
13143     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13144     SendToProgram(buf, cps);
13145     p = q;
13146     if (*p == '=') {
13147       p++;
13148       if (*p == '\"') {
13149         p++;
13150         while (*p && *p != '\"') p++;
13151         if (*p == '\"') p++;
13152       } else {
13153         while (*p && *p != ' ') p++;
13154       }
13155     }
13156   }
13157
13158 }
13159
13160 void
13161 PeriodicUpdatesEvent(newState)
13162      int newState;
13163 {
13164     if (newState == appData.periodicUpdates)
13165       return;
13166
13167     appData.periodicUpdates=newState;
13168
13169     /* Display type changes, so update it now */
13170 //    DisplayAnalysis();
13171
13172     /* Get the ball rolling again... */
13173     if (newState) {
13174         AnalysisPeriodicEvent(1);
13175         StartAnalysisClock();
13176     }
13177 }
13178
13179 void
13180 PonderNextMoveEvent(newState)
13181      int newState;
13182 {
13183     if (newState == appData.ponderNextMove) return;
13184     if (gameMode == EditPosition) EditPositionDone(TRUE);
13185     if (newState) {
13186         SendToProgram("hard\n", &first);
13187         if (gameMode == TwoMachinesPlay) {
13188             SendToProgram("hard\n", &second);
13189         }
13190     } else {
13191         SendToProgram("easy\n", &first);
13192         thinkOutput[0] = NULLCHAR;
13193         if (gameMode == TwoMachinesPlay) {
13194             SendToProgram("easy\n", &second);
13195         }
13196     }
13197     appData.ponderNextMove = newState;
13198 }
13199
13200 void
13201 NewSettingEvent(option, command, value)
13202      char *command;
13203      int option, value;
13204 {
13205     char buf[MSG_SIZ];
13206
13207     if (gameMode == EditPosition) EditPositionDone(TRUE);
13208     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13209     SendToProgram(buf, &first);
13210     if (gameMode == TwoMachinesPlay) {
13211         SendToProgram(buf, &second);
13212     }
13213 }
13214
13215 void
13216 ShowThinkingEvent()
13217 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13218 {
13219     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13220     int newState = appData.showThinking
13221         // [HGM] thinking: other features now need thinking output as well
13222         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13223     
13224     if (oldState == newState) return;
13225     oldState = newState;
13226     if (gameMode == EditPosition) EditPositionDone(TRUE);
13227     if (oldState) {
13228         SendToProgram("post\n", &first);
13229         if (gameMode == TwoMachinesPlay) {
13230             SendToProgram("post\n", &second);
13231         }
13232     } else {
13233         SendToProgram("nopost\n", &first);
13234         thinkOutput[0] = NULLCHAR;
13235         if (gameMode == TwoMachinesPlay) {
13236             SendToProgram("nopost\n", &second);
13237         }
13238     }
13239 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13240 }
13241
13242 void
13243 AskQuestionEvent(title, question, replyPrefix, which)
13244      char *title; char *question; char *replyPrefix; char *which;
13245 {
13246   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13247   if (pr == NoProc) return;
13248   AskQuestion(title, question, replyPrefix, pr);
13249 }
13250
13251 void
13252 DisplayMove(moveNumber)
13253      int moveNumber;
13254 {
13255     char message[MSG_SIZ];
13256     char res[MSG_SIZ];
13257     char cpThinkOutput[MSG_SIZ];
13258
13259     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13260     
13261     if (moveNumber == forwardMostMove - 1 || 
13262         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13263
13264         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13265
13266         if (strchr(cpThinkOutput, '\n')) {
13267             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13268         }
13269     } else {
13270         *cpThinkOutput = NULLCHAR;
13271     }
13272
13273     /* [AS] Hide thinking from human user */
13274     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13275         *cpThinkOutput = NULLCHAR;
13276         if( thinkOutput[0] != NULLCHAR ) {
13277             int i;
13278
13279             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13280                 cpThinkOutput[i] = '.';
13281             }
13282             cpThinkOutput[i] = NULLCHAR;
13283             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13284         }
13285     }
13286
13287     if (moveNumber == forwardMostMove - 1 &&
13288         gameInfo.resultDetails != NULL) {
13289         if (gameInfo.resultDetails[0] == NULLCHAR) {
13290             sprintf(res, " %s", PGNResult(gameInfo.result));
13291         } else {
13292             sprintf(res, " {%s} %s",
13293                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13294         }
13295     } else {
13296         res[0] = NULLCHAR;
13297     }
13298
13299     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13300         DisplayMessage(res, cpThinkOutput);
13301     } else {
13302         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13303                 WhiteOnMove(moveNumber) ? " " : ".. ",
13304                 parseList[moveNumber], res);
13305         DisplayMessage(message, cpThinkOutput);
13306     }
13307 }
13308
13309 void
13310 DisplayComment(moveNumber, text)
13311      int moveNumber;
13312      char *text;
13313 {
13314     char title[MSG_SIZ];
13315     char buf[8000]; // comment can be long!
13316     int score, depth;
13317     
13318     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13319       strcpy(title, "Comment");
13320     } else {
13321       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13322               WhiteOnMove(moveNumber) ? " " : ".. ",
13323               parseList[moveNumber]);
13324     }
13325     // [HGM] PV info: display PV info together with (or as) comment
13326     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13327       if(text == NULL) text = "";                                           
13328       score = pvInfoList[moveNumber].score;
13329       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13330               depth, (pvInfoList[moveNumber].time+50)/100, text);
13331       text = buf;
13332     }
13333     if (text != NULL && (appData.autoDisplayComment || commentUp))
13334         CommentPopUp(title, text);
13335 }
13336
13337 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13338  * might be busy thinking or pondering.  It can be omitted if your
13339  * gnuchess is configured to stop thinking immediately on any user
13340  * input.  However, that gnuchess feature depends on the FIONREAD
13341  * ioctl, which does not work properly on some flavors of Unix.
13342  */
13343 void
13344 Attention(cps)
13345      ChessProgramState *cps;
13346 {
13347 #if ATTENTION
13348     if (!cps->useSigint) return;
13349     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13350     switch (gameMode) {
13351       case MachinePlaysWhite:
13352       case MachinePlaysBlack:
13353       case TwoMachinesPlay:
13354       case IcsPlayingWhite:
13355       case IcsPlayingBlack:
13356       case AnalyzeMode:
13357       case AnalyzeFile:
13358         /* Skip if we know it isn't thinking */
13359         if (!cps->maybeThinking) return;
13360         if (appData.debugMode)
13361           fprintf(debugFP, "Interrupting %s\n", cps->which);
13362         InterruptChildProcess(cps->pr);
13363         cps->maybeThinking = FALSE;
13364         break;
13365       default:
13366         break;
13367     }
13368 #endif /*ATTENTION*/
13369 }
13370
13371 int
13372 CheckFlags()
13373 {
13374     if (whiteTimeRemaining <= 0) {
13375         if (!whiteFlag) {
13376             whiteFlag = TRUE;
13377             if (appData.icsActive) {
13378                 if (appData.autoCallFlag &&
13379                     gameMode == IcsPlayingBlack && !blackFlag) {
13380                   SendToICS(ics_prefix);
13381                   SendToICS("flag\n");
13382                 }
13383             } else {
13384                 if (blackFlag) {
13385                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13386                 } else {
13387                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13388                     if (appData.autoCallFlag) {
13389                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13390                         return TRUE;
13391                     }
13392                 }
13393             }
13394         }
13395     }
13396     if (blackTimeRemaining <= 0) {
13397         if (!blackFlag) {
13398             blackFlag = TRUE;
13399             if (appData.icsActive) {
13400                 if (appData.autoCallFlag &&
13401                     gameMode == IcsPlayingWhite && !whiteFlag) {
13402                   SendToICS(ics_prefix);
13403                   SendToICS("flag\n");
13404                 }
13405             } else {
13406                 if (whiteFlag) {
13407                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13408                 } else {
13409                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13410                     if (appData.autoCallFlag) {
13411                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13412                         return TRUE;
13413                     }
13414                 }
13415             }
13416         }
13417     }
13418     return FALSE;
13419 }
13420
13421 void
13422 CheckTimeControl()
13423 {
13424     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13425         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13426
13427     /*
13428      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13429      */
13430     if ( !WhiteOnMove(forwardMostMove) )
13431         /* White made time control */
13432         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13433         /* [HGM] time odds: correct new time quota for time odds! */
13434                                             / WhitePlayer()->timeOdds;
13435       else
13436         /* Black made time control */
13437         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13438                                             / WhitePlayer()->other->timeOdds;
13439 }
13440
13441 void
13442 DisplayBothClocks()
13443 {
13444     int wom = gameMode == EditPosition ?
13445       !blackPlaysFirst : WhiteOnMove(currentMove);
13446     DisplayWhiteClock(whiteTimeRemaining, wom);
13447     DisplayBlackClock(blackTimeRemaining, !wom);
13448 }
13449
13450
13451 /* Timekeeping seems to be a portability nightmare.  I think everyone
13452    has ftime(), but I'm really not sure, so I'm including some ifdefs
13453    to use other calls if you don't.  Clocks will be less accurate if
13454    you have neither ftime nor gettimeofday.
13455 */
13456
13457 /* VS 2008 requires the #include outside of the function */
13458 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13459 #include <sys/timeb.h>
13460 #endif
13461
13462 /* Get the current time as a TimeMark */
13463 void
13464 GetTimeMark(tm)
13465      TimeMark *tm;
13466 {
13467 #if HAVE_GETTIMEOFDAY
13468
13469     struct timeval timeVal;
13470     struct timezone timeZone;
13471
13472     gettimeofday(&timeVal, &timeZone);
13473     tm->sec = (long) timeVal.tv_sec; 
13474     tm->ms = (int) (timeVal.tv_usec / 1000L);
13475
13476 #else /*!HAVE_GETTIMEOFDAY*/
13477 #if HAVE_FTIME
13478
13479 // include <sys/timeb.h> / moved to just above start of function
13480     struct timeb timeB;
13481
13482     ftime(&timeB);
13483     tm->sec = (long) timeB.time;
13484     tm->ms = (int) timeB.millitm;
13485
13486 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13487     tm->sec = (long) time(NULL);
13488     tm->ms = 0;
13489 #endif
13490 #endif
13491 }
13492
13493 /* Return the difference in milliseconds between two
13494    time marks.  We assume the difference will fit in a long!
13495 */
13496 long
13497 SubtractTimeMarks(tm2, tm1)
13498      TimeMark *tm2, *tm1;
13499 {
13500     return 1000L*(tm2->sec - tm1->sec) +
13501            (long) (tm2->ms - tm1->ms);
13502 }
13503
13504
13505 /*
13506  * Code to manage the game clocks.
13507  *
13508  * In tournament play, black starts the clock and then white makes a move.
13509  * We give the human user a slight advantage if he is playing white---the
13510  * clocks don't run until he makes his first move, so it takes zero time.
13511  * Also, we don't account for network lag, so we could get out of sync
13512  * with GNU Chess's clock -- but then, referees are always right.  
13513  */
13514
13515 static TimeMark tickStartTM;
13516 static long intendedTickLength;
13517
13518 long
13519 NextTickLength(timeRemaining)
13520      long timeRemaining;
13521 {
13522     long nominalTickLength, nextTickLength;
13523
13524     if (timeRemaining > 0L && timeRemaining <= 10000L)
13525       nominalTickLength = 100L;
13526     else
13527       nominalTickLength = 1000L;
13528     nextTickLength = timeRemaining % nominalTickLength;
13529     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13530
13531     return nextTickLength;
13532 }
13533
13534 /* Adjust clock one minute up or down */
13535 void
13536 AdjustClock(Boolean which, int dir)
13537 {
13538     if(which) blackTimeRemaining += 60000*dir;
13539     else      whiteTimeRemaining += 60000*dir;
13540     DisplayBothClocks();
13541 }
13542
13543 /* Stop clocks and reset to a fresh time control */
13544 void
13545 ResetClocks() 
13546 {
13547     (void) StopClockTimer();
13548     if (appData.icsActive) {
13549         whiteTimeRemaining = blackTimeRemaining = 0;
13550     } else if (searchTime) {
13551         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13552         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13553     } else { /* [HGM] correct new time quote for time odds */
13554         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13555         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13556     }
13557     if (whiteFlag || blackFlag) {
13558         DisplayTitle("");
13559         whiteFlag = blackFlag = FALSE;
13560     }
13561     DisplayBothClocks();
13562 }
13563
13564 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13565
13566 /* Decrement running clock by amount of time that has passed */
13567 void
13568 DecrementClocks()
13569 {
13570     long timeRemaining;
13571     long lastTickLength, fudge;
13572     TimeMark now;
13573
13574     if (!appData.clockMode) return;
13575     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13576         
13577     GetTimeMark(&now);
13578
13579     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13580
13581     /* Fudge if we woke up a little too soon */
13582     fudge = intendedTickLength - lastTickLength;
13583     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13584
13585     if (WhiteOnMove(forwardMostMove)) {
13586         if(whiteNPS >= 0) lastTickLength = 0;
13587         timeRemaining = whiteTimeRemaining -= lastTickLength;
13588         DisplayWhiteClock(whiteTimeRemaining - fudge,
13589                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13590     } else {
13591         if(blackNPS >= 0) lastTickLength = 0;
13592         timeRemaining = blackTimeRemaining -= lastTickLength;
13593         DisplayBlackClock(blackTimeRemaining - fudge,
13594                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13595     }
13596
13597     if (CheckFlags()) return;
13598         
13599     tickStartTM = now;
13600     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13601     StartClockTimer(intendedTickLength);
13602
13603     /* if the time remaining has fallen below the alarm threshold, sound the
13604      * alarm. if the alarm has sounded and (due to a takeback or time control
13605      * with increment) the time remaining has increased to a level above the
13606      * threshold, reset the alarm so it can sound again. 
13607      */
13608     
13609     if (appData.icsActive && appData.icsAlarm) {
13610
13611         /* make sure we are dealing with the user's clock */
13612         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13613                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13614            )) return;
13615
13616         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13617             alarmSounded = FALSE;
13618         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13619             PlayAlarmSound();
13620             alarmSounded = TRUE;
13621         }
13622     }
13623 }
13624
13625
13626 /* A player has just moved, so stop the previously running
13627    clock and (if in clock mode) start the other one.
13628    We redisplay both clocks in case we're in ICS mode, because
13629    ICS gives us an update to both clocks after every move.
13630    Note that this routine is called *after* forwardMostMove
13631    is updated, so the last fractional tick must be subtracted
13632    from the color that is *not* on move now.
13633 */
13634 void
13635 SwitchClocks()
13636 {
13637     long lastTickLength;
13638     TimeMark now;
13639     int flagged = FALSE;
13640
13641     GetTimeMark(&now);
13642
13643     if (StopClockTimer() && appData.clockMode) {
13644         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13645         if (WhiteOnMove(forwardMostMove)) {
13646             if(blackNPS >= 0) lastTickLength = 0;
13647             blackTimeRemaining -= lastTickLength;
13648            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13649 //         if(pvInfoList[forwardMostMove-1].time == -1)
13650                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13651                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13652         } else {
13653            if(whiteNPS >= 0) lastTickLength = 0;
13654            whiteTimeRemaining -= lastTickLength;
13655            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13656 //         if(pvInfoList[forwardMostMove-1].time == -1)
13657                  pvInfoList[forwardMostMove-1].time = 
13658                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13659         }
13660         flagged = CheckFlags();
13661     }
13662     CheckTimeControl();
13663
13664     if (flagged || !appData.clockMode) return;
13665
13666     switch (gameMode) {
13667       case MachinePlaysBlack:
13668       case MachinePlaysWhite:
13669       case BeginningOfGame:
13670         if (pausing) return;
13671         break;
13672
13673       case EditGame:
13674       case PlayFromGameFile:
13675       case IcsExamining:
13676         return;
13677
13678       default:
13679         break;
13680     }
13681
13682     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13683         if(WhiteOnMove(forwardMostMove))
13684              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13685         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13686     }
13687
13688     tickStartTM = now;
13689     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13690       whiteTimeRemaining : blackTimeRemaining);
13691     StartClockTimer(intendedTickLength);
13692 }
13693         
13694
13695 /* Stop both clocks */
13696 void
13697 StopClocks()
13698 {       
13699     long lastTickLength;
13700     TimeMark now;
13701
13702     if (!StopClockTimer()) return;
13703     if (!appData.clockMode) return;
13704
13705     GetTimeMark(&now);
13706
13707     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13708     if (WhiteOnMove(forwardMostMove)) {
13709         if(whiteNPS >= 0) lastTickLength = 0;
13710         whiteTimeRemaining -= lastTickLength;
13711         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13712     } else {
13713         if(blackNPS >= 0) lastTickLength = 0;
13714         blackTimeRemaining -= lastTickLength;
13715         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13716     }
13717     CheckFlags();
13718 }
13719         
13720 /* Start clock of player on move.  Time may have been reset, so
13721    if clock is already running, stop and restart it. */
13722 void
13723 StartClocks()
13724 {
13725     (void) StopClockTimer(); /* in case it was running already */
13726     DisplayBothClocks();
13727     if (CheckFlags()) return;
13728
13729     if (!appData.clockMode) return;
13730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13731
13732     GetTimeMark(&tickStartTM);
13733     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13734       whiteTimeRemaining : blackTimeRemaining);
13735
13736    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13737     whiteNPS = blackNPS = -1; 
13738     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13739        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13740         whiteNPS = first.nps;
13741     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13742        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13743         blackNPS = first.nps;
13744     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13745         whiteNPS = second.nps;
13746     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13747         blackNPS = second.nps;
13748     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13749
13750     StartClockTimer(intendedTickLength);
13751 }
13752
13753 char *
13754 TimeString(ms)
13755      long ms;
13756 {
13757     long second, minute, hour, day;
13758     char *sign = "";
13759     static char buf[32];
13760     
13761     if (ms > 0 && ms <= 9900) {
13762       /* convert milliseconds to tenths, rounding up */
13763       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13764
13765       sprintf(buf, " %03.1f ", tenths/10.0);
13766       return buf;
13767     }
13768
13769     /* convert milliseconds to seconds, rounding up */
13770     /* use floating point to avoid strangeness of integer division
13771        with negative dividends on many machines */
13772     second = (long) floor(((double) (ms + 999L)) / 1000.0);
13773
13774     if (second < 0) {
13775         sign = "-";
13776         second = -second;
13777     }
13778     
13779     day = second / (60 * 60 * 24);
13780     second = second % (60 * 60 * 24);
13781     hour = second / (60 * 60);
13782     second = second % (60 * 60);
13783     minute = second / 60;
13784     second = second % 60;
13785     
13786     if (day > 0)
13787       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13788               sign, day, hour, minute, second);
13789     else if (hour > 0)
13790       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13791     else
13792       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13793     
13794     return buf;
13795 }
13796
13797
13798 /*
13799  * This is necessary because some C libraries aren't ANSI C compliant yet.
13800  */
13801 char *
13802 StrStr(string, match)
13803      char *string, *match;
13804 {
13805     int i, length;
13806     
13807     length = strlen(match);
13808     
13809     for (i = strlen(string) - length; i >= 0; i--, string++)
13810       if (!strncmp(match, string, length))
13811         return string;
13812     
13813     return NULL;
13814 }
13815
13816 char *
13817 StrCaseStr(string, match)
13818      char *string, *match;
13819 {
13820     int i, j, length;
13821     
13822     length = strlen(match);
13823     
13824     for (i = strlen(string) - length; i >= 0; i--, string++) {
13825         for (j = 0; j < length; j++) {
13826             if (ToLower(match[j]) != ToLower(string[j]))
13827               break;
13828         }
13829         if (j == length) return string;
13830     }
13831
13832     return NULL;
13833 }
13834
13835 #ifndef _amigados
13836 int
13837 StrCaseCmp(s1, s2)
13838      char *s1, *s2;
13839 {
13840     char c1, c2;
13841     
13842     for (;;) {
13843         c1 = ToLower(*s1++);
13844         c2 = ToLower(*s2++);
13845         if (c1 > c2) return 1;
13846         if (c1 < c2) return -1;
13847         if (c1 == NULLCHAR) return 0;
13848     }
13849 }
13850
13851
13852 int
13853 ToLower(c)
13854      int c;
13855 {
13856     return isupper(c) ? tolower(c) : c;
13857 }
13858
13859
13860 int
13861 ToUpper(c)
13862      int c;
13863 {
13864     return islower(c) ? toupper(c) : c;
13865 }
13866 #endif /* !_amigados    */
13867
13868 char *
13869 StrSave(s)
13870      char *s;
13871 {
13872     char *ret;
13873
13874     if ((ret = (char *) malloc(strlen(s) + 1))) {
13875         strcpy(ret, s);
13876     }
13877     return ret;
13878 }
13879
13880 char *
13881 StrSavePtr(s, savePtr)
13882      char *s, **savePtr;
13883 {
13884     if (*savePtr) {
13885         free(*savePtr);
13886     }
13887     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13888         strcpy(*savePtr, s);
13889     }
13890     return(*savePtr);
13891 }
13892
13893 char *
13894 PGNDate()
13895 {
13896     time_t clock;
13897     struct tm *tm;
13898     char buf[MSG_SIZ];
13899
13900     clock = time((time_t *)NULL);
13901     tm = localtime(&clock);
13902     sprintf(buf, "%04d.%02d.%02d",
13903             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13904     return StrSave(buf);
13905 }
13906
13907
13908 char *
13909 PositionToFEN(move, overrideCastling)
13910      int move;
13911      char *overrideCastling;
13912 {
13913     int i, j, fromX, fromY, toX, toY;
13914     int whiteToPlay;
13915     char buf[128];
13916     char *p, *q;
13917     int emptycount;
13918     ChessSquare piece;
13919
13920     whiteToPlay = (gameMode == EditPosition) ?
13921       !blackPlaysFirst : (move % 2 == 0);
13922     p = buf;
13923
13924     /* Piece placement data */
13925     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13926         emptycount = 0;
13927         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13928             if (boards[move][i][j] == EmptySquare) {
13929                 emptycount++;
13930             } else { ChessSquare piece = boards[move][i][j];
13931                 if (emptycount > 0) {
13932                     if(emptycount<10) /* [HGM] can be >= 10 */
13933                         *p++ = '0' + emptycount;
13934                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13935                     emptycount = 0;
13936                 }
13937                 if(PieceToChar(piece) == '+') {
13938                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13939                     *p++ = '+';
13940                     piece = (ChessSquare)(DEMOTED piece);
13941                 } 
13942                 *p++ = PieceToChar(piece);
13943                 if(p[-1] == '~') {
13944                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13945                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13946                     *p++ = '~';
13947                 }
13948             }
13949         }
13950         if (emptycount > 0) {
13951             if(emptycount<10) /* [HGM] can be >= 10 */
13952                 *p++ = '0' + emptycount;
13953             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13954             emptycount = 0;
13955         }
13956         *p++ = '/';
13957     }
13958     *(p - 1) = ' ';
13959
13960     /* [HGM] print Crazyhouse or Shogi holdings */
13961     if( gameInfo.holdingsWidth ) {
13962         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13963         q = p;
13964         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13965             piece = boards[move][i][BOARD_WIDTH-1];
13966             if( piece != EmptySquare )
13967               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13968                   *p++ = PieceToChar(piece);
13969         }
13970         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13971             piece = boards[move][BOARD_HEIGHT-i-1][0];
13972             if( piece != EmptySquare )
13973               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13974                   *p++ = PieceToChar(piece);
13975         }
13976
13977         if( q == p ) *p++ = '-';
13978         *p++ = ']';
13979         *p++ = ' ';
13980     }
13981
13982     /* Active color */
13983     *p++ = whiteToPlay ? 'w' : 'b';
13984     *p++ = ' ';
13985
13986   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13987     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13988   } else {
13989   if(nrCastlingRights) {
13990      q = p;
13991      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13992        /* [HGM] write directly from rights */
13993            if(boards[move][CASTLING][2] != NoRights &&
13994               boards[move][CASTLING][0] != NoRights   )
13995                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13996            if(boards[move][CASTLING][2] != NoRights &&
13997               boards[move][CASTLING][1] != NoRights   )
13998                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13999            if(boards[move][CASTLING][5] != NoRights &&
14000               boards[move][CASTLING][3] != NoRights   )
14001                 *p++ = boards[move][CASTLING][3] + AAA;
14002            if(boards[move][CASTLING][5] != NoRights &&
14003               boards[move][CASTLING][4] != NoRights   )
14004                 *p++ = boards[move][CASTLING][4] + AAA;
14005      } else {
14006
14007         /* [HGM] write true castling rights */
14008         if( nrCastlingRights == 6 ) {
14009             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14010                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14011             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14012                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14013             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14014                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14015             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14016                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14017         }
14018      }
14019      if (q == p) *p++ = '-'; /* No castling rights */
14020      *p++ = ' ';
14021   }
14022
14023   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14024      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14025     /* En passant target square */
14026     if (move > backwardMostMove) {
14027         fromX = moveList[move - 1][0] - AAA;
14028         fromY = moveList[move - 1][1] - ONE;
14029         toX = moveList[move - 1][2] - AAA;
14030         toY = moveList[move - 1][3] - ONE;
14031         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14032             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14033             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14034             fromX == toX) {
14035             /* 2-square pawn move just happened */
14036             *p++ = toX + AAA;
14037             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14038         } else {
14039             *p++ = '-';
14040         }
14041     } else if(move == backwardMostMove) {
14042         // [HGM] perhaps we should always do it like this, and forget the above?
14043         if((signed char)boards[move][EP_STATUS] >= 0) {
14044             *p++ = boards[move][EP_STATUS] + AAA;
14045             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14046         } else {
14047             *p++ = '-';
14048         }
14049     } else {
14050         *p++ = '-';
14051     }
14052     *p++ = ' ';
14053   }
14054   }
14055
14056     /* [HGM] find reversible plies */
14057     {   int i = 0, j=move;
14058
14059         if (appData.debugMode) { int k;
14060             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14061             for(k=backwardMostMove; k<=forwardMostMove; k++)
14062                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14063
14064         }
14065
14066         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14067         if( j == backwardMostMove ) i += initialRulePlies;
14068         sprintf(p, "%d ", i);
14069         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14070     }
14071     /* Fullmove number */
14072     sprintf(p, "%d", (move / 2) + 1);
14073     
14074     return StrSave(buf);
14075 }
14076
14077 Boolean
14078 ParseFEN(board, blackPlaysFirst, fen)
14079     Board board;
14080      int *blackPlaysFirst;
14081      char *fen;
14082 {
14083     int i, j;
14084     char *p;
14085     int emptycount;
14086     ChessSquare piece;
14087
14088     p = fen;
14089
14090     /* [HGM] by default clear Crazyhouse holdings, if present */
14091     if(gameInfo.holdingsWidth) {
14092        for(i=0; i<BOARD_HEIGHT; i++) {
14093            board[i][0]             = EmptySquare; /* black holdings */
14094            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14095            board[i][1]             = (ChessSquare) 0; /* black counts */
14096            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14097        }
14098     }
14099
14100     /* Piece placement data */
14101     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14102         j = 0;
14103         for (;;) {
14104             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14105                 if (*p == '/') p++;
14106                 emptycount = gameInfo.boardWidth - j;
14107                 while (emptycount--)
14108                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14109                 break;
14110 #if(BOARD_FILES >= 10)
14111             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14112                 p++; emptycount=10;
14113                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14114                 while (emptycount--)
14115                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14116 #endif
14117             } else if (isdigit(*p)) {
14118                 emptycount = *p++ - '0';
14119                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14120                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14121                 while (emptycount--)
14122                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14123             } else if (*p == '+' || isalpha(*p)) {
14124                 if (j >= gameInfo.boardWidth) return FALSE;
14125                 if(*p=='+') {
14126                     piece = CharToPiece(*++p);
14127                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14128                     piece = (ChessSquare) (PROMOTED piece ); p++;
14129                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14130                 } else piece = CharToPiece(*p++);
14131
14132                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14133                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14134                     piece = (ChessSquare) (PROMOTED piece);
14135                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14136                     p++;
14137                 }
14138                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14139             } else {
14140                 return FALSE;
14141             }
14142         }
14143     }
14144     while (*p == '/' || *p == ' ') p++;
14145
14146     /* [HGM] look for Crazyhouse holdings here */
14147     while(*p==' ') p++;
14148     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14149         if(*p == '[') p++;
14150         if(*p == '-' ) *p++; /* empty holdings */ else {
14151             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14152             /* if we would allow FEN reading to set board size, we would   */
14153             /* have to add holdings and shift the board read so far here   */
14154             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14155                 *p++;
14156                 if((int) piece >= (int) BlackPawn ) {
14157                     i = (int)piece - (int)BlackPawn;
14158                     i = PieceToNumber((ChessSquare)i);
14159                     if( i >= gameInfo.holdingsSize ) return FALSE;
14160                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14161                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14162                 } else {
14163                     i = (int)piece - (int)WhitePawn;
14164                     i = PieceToNumber((ChessSquare)i);
14165                     if( i >= gameInfo.holdingsSize ) return FALSE;
14166                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14167                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14168                 }
14169             }
14170         }
14171         if(*p == ']') *p++;
14172     }
14173
14174     while(*p == ' ') p++;
14175
14176     /* Active color */
14177     switch (*p++) {
14178       case 'w':
14179         *blackPlaysFirst = FALSE;
14180         break;
14181       case 'b': 
14182         *blackPlaysFirst = TRUE;
14183         break;
14184       default:
14185         return FALSE;
14186     }
14187
14188     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14189     /* return the extra info in global variiables             */
14190
14191     /* set defaults in case FEN is incomplete */
14192     board[EP_STATUS] = EP_UNKNOWN;
14193     for(i=0; i<nrCastlingRights; i++ ) {
14194         board[CASTLING][i] =
14195             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14196     }   /* assume possible unless obviously impossible */
14197     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14198     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14199     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14200                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14201     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14202     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14203     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14204                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14205     FENrulePlies = 0;
14206
14207     while(*p==' ') p++;
14208     if(nrCastlingRights) {
14209       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14210           /* castling indicator present, so default becomes no castlings */
14211           for(i=0; i<nrCastlingRights; i++ ) {
14212                  board[CASTLING][i] = NoRights;
14213           }
14214       }
14215       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14216              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14217              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14218              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14219         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14220
14221         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14222             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14223             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14224         }
14225         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14226             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14227         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14228                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14229         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14230                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14231         switch(c) {
14232           case'K':
14233               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14234               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14235               board[CASTLING][2] = whiteKingFile;
14236               break;
14237           case'Q':
14238               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14239               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14240               board[CASTLING][2] = whiteKingFile;
14241               break;
14242           case'k':
14243               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14244               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14245               board[CASTLING][5] = blackKingFile;
14246               break;
14247           case'q':
14248               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14249               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14250               board[CASTLING][5] = blackKingFile;
14251           case '-':
14252               break;
14253           default: /* FRC castlings */
14254               if(c >= 'a') { /* black rights */
14255                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14256                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14257                   if(i == BOARD_RGHT) break;
14258                   board[CASTLING][5] = i;
14259                   c -= AAA;
14260                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14261                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14262                   if(c > i)
14263                       board[CASTLING][3] = c;
14264                   else
14265                       board[CASTLING][4] = c;
14266               } else { /* white rights */
14267                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14268                     if(board[0][i] == WhiteKing) break;
14269                   if(i == BOARD_RGHT) break;
14270                   board[CASTLING][2] = i;
14271                   c -= AAA - 'a' + 'A';
14272                   if(board[0][c] >= WhiteKing) break;
14273                   if(c > i)
14274                       board[CASTLING][0] = c;
14275                   else
14276                       board[CASTLING][1] = c;
14277               }
14278         }
14279       }
14280       for(i=0; i<nrCastlingRights; i++)
14281         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14282     if (appData.debugMode) {
14283         fprintf(debugFP, "FEN castling rights:");
14284         for(i=0; i<nrCastlingRights; i++)
14285         fprintf(debugFP, " %d", board[CASTLING][i]);
14286         fprintf(debugFP, "\n");
14287     }
14288
14289       while(*p==' ') p++;
14290     }
14291
14292     /* read e.p. field in games that know e.p. capture */
14293     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14294        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14295       if(*p=='-') {
14296         p++; board[EP_STATUS] = EP_NONE;
14297       } else {
14298          char c = *p++ - AAA;
14299
14300          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14301          if(*p >= '0' && *p <='9') *p++;
14302          board[EP_STATUS] = c;
14303       }
14304     }
14305
14306
14307     if(sscanf(p, "%d", &i) == 1) {
14308         FENrulePlies = i; /* 50-move ply counter */
14309         /* (The move number is still ignored)    */
14310     }
14311
14312     return TRUE;
14313 }
14314       
14315 void
14316 EditPositionPasteFEN(char *fen)
14317 {
14318   if (fen != NULL) {
14319     Board initial_position;
14320
14321     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14322       DisplayError(_("Bad FEN position in clipboard"), 0);
14323       return ;
14324     } else {
14325       int savedBlackPlaysFirst = blackPlaysFirst;
14326       EditPositionEvent();
14327       blackPlaysFirst = savedBlackPlaysFirst;
14328       CopyBoard(boards[0], initial_position);
14329       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14330       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14331       DisplayBothClocks();
14332       DrawPosition(FALSE, boards[currentMove]);
14333     }
14334   }
14335 }
14336
14337 static char cseq[12] = "\\   ";
14338
14339 Boolean set_cont_sequence(char *new_seq)
14340 {
14341     int len;
14342     Boolean ret;
14343
14344     // handle bad attempts to set the sequence
14345         if (!new_seq)
14346                 return 0; // acceptable error - no debug
14347
14348     len = strlen(new_seq);
14349     ret = (len > 0) && (len < sizeof(cseq));
14350     if (ret)
14351         strcpy(cseq, new_seq);
14352     else if (appData.debugMode)
14353         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14354     return ret;
14355 }
14356
14357 /*
14358     reformat a source message so words don't cross the width boundary.  internal
14359     newlines are not removed.  returns the wrapped size (no null character unless
14360     included in source message).  If dest is NULL, only calculate the size required
14361     for the dest buffer.  lp argument indicats line position upon entry, and it's
14362     passed back upon exit.
14363 */
14364 int wrap(char *dest, char *src, int count, int width, int *lp)
14365 {
14366     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14367
14368     cseq_len = strlen(cseq);
14369     old_line = line = *lp;
14370     ansi = len = clen = 0;
14371
14372     for (i=0; i < count; i++)
14373     {
14374         if (src[i] == '\033')
14375             ansi = 1;
14376
14377         // if we hit the width, back up
14378         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14379         {
14380             // store i & len in case the word is too long
14381             old_i = i, old_len = len;
14382
14383             // find the end of the last word
14384             while (i && src[i] != ' ' && src[i] != '\n')
14385             {
14386                 i--;
14387                 len--;
14388             }
14389
14390             // word too long?  restore i & len before splitting it
14391             if ((old_i-i+clen) >= width)
14392             {
14393                 i = old_i;
14394                 len = old_len;
14395             }
14396
14397             // extra space?
14398             if (i && src[i-1] == ' ')
14399                 len--;
14400
14401             if (src[i] != ' ' && src[i] != '\n')
14402             {
14403                 i--;
14404                 if (len)
14405                     len--;
14406             }
14407
14408             // now append the newline and continuation sequence
14409             if (dest)
14410                 dest[len] = '\n';
14411             len++;
14412             if (dest)
14413                 strncpy(dest+len, cseq, cseq_len);
14414             len += cseq_len;
14415             line = cseq_len;
14416             clen = cseq_len;
14417             continue;
14418         }
14419
14420         if (dest)
14421             dest[len] = src[i];
14422         len++;
14423         if (!ansi)
14424             line++;
14425         if (src[i] == '\n')
14426             line = 0;
14427         if (src[i] == 'm')
14428             ansi = 0;
14429     }
14430     if (dest && appData.debugMode)
14431     {
14432         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14433             count, width, line, len, *lp);
14434         show_bytes(debugFP, src, count);
14435         fprintf(debugFP, "\ndest: ");
14436         show_bytes(debugFP, dest, len);
14437         fprintf(debugFP, "\n");
14438     }
14439     *lp = dest ? line : old_line;
14440
14441     return len;
14442 }
14443
14444 // [HGM] vari: routines for shelving variations
14445
14446 void 
14447 PushTail(int firstMove, int lastMove)
14448 {
14449         int i, j, nrMoves = lastMove - firstMove;
14450
14451         if(appData.icsActive) { // only in local mode
14452                 forwardMostMove = currentMove; // mimic old ICS behavior
14453                 return;
14454         }
14455         if(storedGames >= MAX_VARIATIONS-1) return;
14456
14457         // push current tail of game on stack
14458         savedResult[storedGames] = gameInfo.result;
14459         savedDetails[storedGames] = gameInfo.resultDetails;
14460         gameInfo.resultDetails = NULL;
14461         savedFirst[storedGames] = firstMove;
14462         savedLast [storedGames] = lastMove;
14463         savedFramePtr[storedGames] = framePtr;
14464         framePtr -= nrMoves; // reserve space for the boards
14465         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14466             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14467             for(j=0; j<MOVE_LEN; j++)
14468                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14469             for(j=0; j<2*MOVE_LEN; j++)
14470                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14471             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14472             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14473             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14474             pvInfoList[firstMove+i-1].depth = 0;
14475             commentList[framePtr+i] = commentList[firstMove+i];
14476             commentList[firstMove+i] = NULL;
14477         }
14478
14479         storedGames++;
14480         forwardMostMove = currentMove; // truncte game so we can start variation
14481         if(storedGames == 1) GreyRevert(FALSE);
14482 }
14483
14484 Boolean
14485 PopTail(Boolean annotate)
14486 {
14487         int i, j, nrMoves;
14488         char buf[8000], moveBuf[20];
14489
14490         if(appData.icsActive) return FALSE; // only in local mode
14491         if(!storedGames) return FALSE; // sanity
14492
14493         storedGames--;
14494         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14495         nrMoves = savedLast[storedGames] - currentMove;
14496         if(annotate) {
14497                 int cnt = 10;
14498                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14499                 else strcpy(buf, "(");
14500                 for(i=currentMove; i<forwardMostMove; i++) {
14501                         if(WhiteOnMove(i))
14502                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14503                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14504                         strcat(buf, moveBuf);
14505                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14506                 }
14507                 strcat(buf, ")");
14508         }
14509         for(i=1; i<nrMoves; i++) { // copy last variation back
14510             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14511             for(j=0; j<MOVE_LEN; j++)
14512                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14513             for(j=0; j<2*MOVE_LEN; j++)
14514                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14515             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14516             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14517             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14518             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14519             commentList[currentMove+i] = commentList[framePtr+i];
14520             commentList[framePtr+i] = NULL;
14521         }
14522         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14523         framePtr = savedFramePtr[storedGames];
14524         gameInfo.result = savedResult[storedGames];
14525         if(gameInfo.resultDetails != NULL) {
14526             free(gameInfo.resultDetails);
14527       }
14528         gameInfo.resultDetails = savedDetails[storedGames];
14529         forwardMostMove = currentMove + nrMoves;
14530         if(storedGames == 0) GreyRevert(TRUE);
14531         return TRUE;
14532 }
14533
14534 void 
14535 CleanupTail()
14536 {       // remove all shelved variations
14537         int i;
14538         for(i=0; i<storedGames; i++) {
14539             if(savedDetails[i])
14540                 free(savedDetails[i]);
14541             savedDetails[i] = NULL;
14542         }
14543         for(i=framePtr; i<MAX_MOVES; i++) {
14544                 if(commentList[i]) free(commentList[i]);
14545                 commentList[i] = NULL;
14546         }
14547         framePtr = MAX_MOVES-1;
14548         storedGames = 0;
14549 }