dedf3338071f126f49b650c700a4c79c4f53d395
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
254 int chattingPartner;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256
257 /* States for ics_getting_history */
258 #define H_FALSE 0
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
264
265 /* whosays values for GameEnds */
266 #define GE_ICS 0
267 #define GE_ENGINE 1
268 #define GE_PLAYER 2
269 #define GE_FILE 3
270 #define GE_XBOARD 4
271 #define GE_ENGINE1 5
272 #define GE_ENGINE2 6
273
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
276
277 /* Different types of move when calling RegisterMove */
278 #define CMAIL_MOVE   0
279 #define CMAIL_RESIGN 1
280 #define CMAIL_DRAW   2
281 #define CMAIL_ACCEPT 3
282
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
287
288 /* Telnet protocol constants */
289 #define TN_WILL 0373
290 #define TN_WONT 0374
291 #define TN_DO   0375
292 #define TN_DONT 0376
293 #define TN_IAC  0377
294 #define TN_ECHO 0001
295 #define TN_SGA  0003
296 #define TN_PORT 23
297
298 /* [AS] */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 {
301     assert( dst != NULL );
302     assert( src != NULL );
303     assert( count > 0 );
304
305     strncpy( dst, src, count );
306     dst[ count-1 ] = '\0';
307     return dst;
308 }
309
310 /* Some compiler can't cast u64 to double
311  * This function do the job for us:
312
313  * We use the highest bit for cast, this only
314  * works if the highest bit is not
315  * in use (This should not happen)
316  *
317  * We used this for all compiler
318  */
319 double
320 u64ToDouble(u64 value)
321 {
322   double r;
323   u64 tmp = value & u64Const(0x7fffffffffffffff);
324   r = (double)(s64)tmp;
325   if (value & u64Const(0x8000000000000000))
326        r +=  9.2233720368547758080e18; /* 2^63 */
327  return r;
328 }
329
330 /* Fake up flags for now, as we aren't keeping track of castling
331    availability yet. [HGM] Change of logic: the flag now only
332    indicates the type of castlings allowed by the rule of the game.
333    The actual rights themselves are maintained in the array
334    castlingRights, as part of the game history, and are not probed
335    by this function.
336  */
337 int
338 PosFlags(index)
339 {
340   int flags = F_ALL_CASTLE_OK;
341   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342   switch (gameInfo.variant) {
343   case VariantSuicide:
344     flags &= ~F_ALL_CASTLE_OK;
345   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346     flags |= F_IGNORE_CHECK;
347   case VariantLosers:
348     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
349     break;
350   case VariantAtomic:
351     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352     break;
353   case VariantKriegspiel:
354     flags |= F_KRIEGSPIEL_CAPTURE;
355     break;
356   case VariantCapaRandom: 
357   case VariantFischeRandom:
358     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359   case VariantNoCastle:
360   case VariantShatranj:
361   case VariantCourier:
362   case VariantMakruk:
363     flags &= ~F_ALL_CASTLE_OK;
364     break;
365   default:
366     break;
367   }
368   return flags;
369 }
370
371 FILE *gameFileFP, *debugFP;
372
373 /* 
374     [AS] Note: sometimes, the sscanf() function is used to parse the input
375     into a fixed-size buffer. Because of this, we must be prepared to
376     receive strings as long as the size of the input buffer, which is currently
377     set to 4K for Windows and 8K for the rest.
378     So, we must either allocate sufficiently large buffers here, or
379     reduce the size of the input buffer in the input reading part.
380 */
381
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
385
386 ChessProgramState first, second;
387
388 /* premove variables */
389 int premoveToX = 0;
390 int premoveToY = 0;
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
394 int gotPremove = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
397
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
400
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
428 int movesPerSession;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
433 int matchGame = 0;
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
437
438 /* animateTraining preserves the state of appData.animate
439  * when Training mode is activated. This allows the
440  * response to be animated when appData.animate == TRUE and
441  * appData.animateDragging == TRUE.
442  */
443 Boolean animateTraining;
444
445 GameInfo gameInfo;
446
447 AppData appData;
448
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char  initialRights[BOARD_FILES];
453 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int   initialRulePlies, FENrulePlies;
455 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
456 int loadFlag = 0; 
457 int shuffleOpenings;
458 int mute; // mute all sounds
459
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int storedGames = 0;
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
469
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
473
474 ChessSquare  FIDEArray[2][BOARD_FILES] = {
475     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478         BlackKing, BlackBishop, BlackKnight, BlackRook }
479 };
480
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485         BlackKing, BlackKing, BlackKnight, BlackRook }
486 };
487
488 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
489     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491     { BlackRook, BlackMan, BlackBishop, BlackQueen,
492         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
493 };
494
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
500 };
501
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 };
508
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512     { BlackRook, BlackKnight, BlackMan, BlackFerz,
513         BlackKing, BlackMan, BlackKnight, BlackRook }
514 };
515
516
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
523 };
524
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
530 };
531
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
534         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
536         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
537 };
538
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
541         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
543         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
544 };
545
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
548         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
550         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
551 };
552
553 #ifdef GOTHIC
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
556         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
558         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
559 };
560 #else // !GOTHIC
561 #define GothicArray CapablancaArray
562 #endif // !GOTHIC
563
564 #ifdef FALCON
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
567         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
569         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
570 };
571 #else // !FALCON
572 #define FalconArray CapablancaArray
573 #endif // !FALCON
574
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
581
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 };
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
592
593
594 Board initialPosition;
595
596
597 /* Convert str to a rating. Checks for special cases of "----",
598
599    "++++", etc. Also strips ()'s */
600 int
601 string_to_rating(str)
602   char *str;
603 {
604   while(*str && !isdigit(*str)) ++str;
605   if (!*str)
606     return 0;   /* One of the special "no rating" cases */
607   else
608     return atoi(str);
609 }
610
611 void
612 ClearProgramStats()
613 {
614     /* Init programStats */
615     programStats.movelist[0] = 0;
616     programStats.depth = 0;
617     programStats.nr_moves = 0;
618     programStats.moves_left = 0;
619     programStats.nodes = 0;
620     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
621     programStats.score = 0;
622     programStats.got_only_move = 0;
623     programStats.got_fail = 0;
624     programStats.line_is_book = 0;
625 }
626
627 void
628 InitBackEnd1()
629 {
630     int matched, min, sec;
631
632     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633
634     GetTimeMark(&programStartTime);
635     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
636
637     ClearProgramStats();
638     programStats.ok_to_send = 1;
639     programStats.seen_stat = 0;
640
641     /*
642      * Initialize game list
643      */
644     ListNew(&gameList);
645
646
647     /*
648      * Internet chess server status
649      */
650     if (appData.icsActive) {
651         appData.matchMode = FALSE;
652         appData.matchGames = 0;
653 #if ZIPPY       
654         appData.noChessProgram = !appData.zippyPlay;
655 #else
656         appData.zippyPlay = FALSE;
657         appData.zippyTalk = FALSE;
658         appData.noChessProgram = TRUE;
659 #endif
660         if (*appData.icsHelper != NULLCHAR) {
661             appData.useTelnet = TRUE;
662             appData.telnetProgram = appData.icsHelper;
663         }
664     } else {
665         appData.zippyTalk = appData.zippyPlay = FALSE;
666     }
667
668     /* [AS] Initialize pv info list [HGM] and game state */
669     {
670         int i, j;
671
672         for( i=0; i<=framePtr; i++ ) {
673             pvInfoList[i].depth = -1;
674             boards[i][EP_STATUS] = EP_NONE;
675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
676         }
677     }
678
679     /*
680      * Parse timeControl resource
681      */
682     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683                           appData.movesPerSession)) {
684         char buf[MSG_SIZ];
685         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686         DisplayFatalError(buf, 0, 2);
687     }
688
689     /*
690      * Parse searchTime resource
691      */
692     if (*appData.searchTime != NULLCHAR) {
693         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694         if (matched == 1) {
695             searchTime = min * 60;
696         } else if (matched == 2) {
697             searchTime = min * 60 + sec;
698         } else {
699             char buf[MSG_SIZ];
700             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701             DisplayFatalError(buf, 0, 2);
702         }
703     }
704
705     /* [AS] Adjudication threshold */
706     adjudicateLossThreshold = appData.adjudicateLossThreshold;
707     
708     first.which = "first";
709     second.which = "second";
710     first.maybeThinking = second.maybeThinking = FALSE;
711     first.pr = second.pr = NoProc;
712     first.isr = second.isr = NULL;
713     first.sendTime = second.sendTime = 2;
714     first.sendDrawOffers = 1;
715     if (appData.firstPlaysBlack) {
716         first.twoMachinesColor = "black\n";
717         second.twoMachinesColor = "white\n";
718     } else {
719         first.twoMachinesColor = "white\n";
720         second.twoMachinesColor = "black\n";
721     }
722     first.program = appData.firstChessProgram;
723     second.program = appData.secondChessProgram;
724     first.host = appData.firstHost;
725     second.host = appData.secondHost;
726     first.dir = appData.firstDirectory;
727     second.dir = appData.secondDirectory;
728     first.other = &second;
729     second.other = &first;
730     first.initString = appData.initString;
731     second.initString = appData.secondInitString;
732     first.computerString = appData.firstComputerString;
733     second.computerString = appData.secondComputerString;
734     first.useSigint = second.useSigint = TRUE;
735     first.useSigterm = second.useSigterm = TRUE;
736     first.reuse = appData.reuseFirst;
737     second.reuse = appData.reuseSecond;
738     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
739     second.nps = appData.secondNPS;
740     first.useSetboard = second.useSetboard = FALSE;
741     first.useSAN = second.useSAN = FALSE;
742     first.usePing = second.usePing = FALSE;
743     first.lastPing = second.lastPing = 0;
744     first.lastPong = second.lastPong = 0;
745     first.usePlayother = second.usePlayother = FALSE;
746     first.useColors = second.useColors = TRUE;
747     first.useUsermove = second.useUsermove = FALSE;
748     first.sendICS = second.sendICS = FALSE;
749     first.sendName = second.sendName = appData.icsActive;
750     first.sdKludge = second.sdKludge = FALSE;
751     first.stKludge = second.stKludge = FALSE;
752     TidyProgramName(first.program, first.host, first.tidy);
753     TidyProgramName(second.program, second.host, second.tidy);
754     first.matchWins = second.matchWins = 0;
755     strcpy(first.variants, appData.variant);
756     strcpy(second.variants, appData.variant);
757     first.analysisSupport = second.analysisSupport = 2; /* detect */
758     first.analyzing = second.analyzing = FALSE;
759     first.initDone = second.initDone = FALSE;
760
761     /* New features added by Tord: */
762     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764     /* End of new features added by Tord. */
765     first.fenOverride  = appData.fenOverride1;
766     second.fenOverride = appData.fenOverride2;
767
768     /* [HGM] time odds: set factor for each machine */
769     first.timeOdds  = appData.firstTimeOdds;
770     second.timeOdds = appData.secondTimeOdds;
771     { float norm = 1;
772         if(appData.timeOddsMode) {
773             norm = first.timeOdds;
774             if(norm > second.timeOdds) norm = second.timeOdds;
775         }
776         first.timeOdds /= norm;
777         second.timeOdds /= norm;
778     }
779
780     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781     first.accumulateTC = appData.firstAccumulateTC;
782     second.accumulateTC = appData.secondAccumulateTC;
783     first.maxNrOfSessions = second.maxNrOfSessions = 1;
784
785     /* [HGM] debug */
786     first.debug = second.debug = FALSE;
787     first.supportsNPS = second.supportsNPS = UNKNOWN;
788
789     /* [HGM] options */
790     first.optionSettings  = appData.firstOptions;
791     second.optionSettings = appData.secondOptions;
792
793     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795     first.isUCI = appData.firstIsUCI; /* [AS] */
796     second.isUCI = appData.secondIsUCI; /* [AS] */
797     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799
800     if (appData.firstProtocolVersion > PROTOVER ||
801         appData.firstProtocolVersion < 1) {
802       char buf[MSG_SIZ];
803       sprintf(buf, _("protocol version %d not supported"),
804               appData.firstProtocolVersion);
805       DisplayFatalError(buf, 0, 2);
806     } else {
807       first.protocolVersion = appData.firstProtocolVersion;
808     }
809
810     if (appData.secondProtocolVersion > PROTOVER ||
811         appData.secondProtocolVersion < 1) {
812       char buf[MSG_SIZ];
813       sprintf(buf, _("protocol version %d not supported"),
814               appData.secondProtocolVersion);
815       DisplayFatalError(buf, 0, 2);
816     } else {
817       second.protocolVersion = appData.secondProtocolVersion;
818     }
819
820     if (appData.icsActive) {
821         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
822 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824         appData.clockMode = FALSE;
825         first.sendTime = second.sendTime = 0;
826     }
827     
828 #if ZIPPY
829     /* Override some settings from environment variables, for backward
830        compatibility.  Unfortunately it's not feasible to have the env
831        vars just set defaults, at least in xboard.  Ugh.
832     */
833     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
834       ZippyInit();
835     }
836 #endif
837     
838     if (appData.noChessProgram) {
839         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840         sprintf(programVersion, "%s", PACKAGE_STRING);
841     } else {
842       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
845     }
846
847     if (!appData.icsActive) {
848       char buf[MSG_SIZ];
849       /* Check for variants that are supported only in ICS mode,
850          or not at all.  Some that are accepted here nevertheless
851          have bugs; see comments below.
852       */
853       VariantClass variant = StringToVariant(appData.variant);
854       switch (variant) {
855       case VariantBughouse:     /* need four players and two boards */
856       case VariantKriegspiel:   /* need to hide pieces and move details */
857       /* case VariantFischeRandom: (Fabien: moved below) */
858         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859         DisplayFatalError(buf, 0, 2);
860         return;
861
862       case VariantUnknown:
863       case VariantLoadable:
864       case Variant29:
865       case Variant30:
866       case Variant31:
867       case Variant32:
868       case Variant33:
869       case Variant34:
870       case Variant35:
871       case Variant36:
872       default:
873         sprintf(buf, _("Unknown variant name %s"), appData.variant);
874         DisplayFatalError(buf, 0, 2);
875         return;
876
877       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
878       case VariantFairy:      /* [HGM] TestLegality definitely off! */
879       case VariantGothic:     /* [HGM] should work */
880       case VariantCapablanca: /* [HGM] should work */
881       case VariantCourier:    /* [HGM] initial forced moves not implemented */
882       case VariantShogi:      /* [HGM] drops not tested for legality */
883       case VariantKnightmate: /* [HGM] should work */
884       case VariantCylinder:   /* [HGM] untested */
885       case VariantFalcon:     /* [HGM] untested */
886       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887                                  offboard interposition not understood */
888       case VariantNormal:     /* definitely works! */
889       case VariantWildCastle: /* pieces not automatically shuffled */
890       case VariantNoCastle:   /* pieces not automatically shuffled */
891       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892       case VariantLosers:     /* should work except for win condition,
893                                  and doesn't know captures are mandatory */
894       case VariantSuicide:    /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantGiveaway:   /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantTwoKings:   /* should work */
899       case VariantAtomic:     /* should work except for win condition */
900       case Variant3Check:     /* should work except for win condition */
901       case VariantShatranj:   /* should work except for all win conditions */
902       case VariantMakruk:     /* should work except for daw countdown */
903       case VariantBerolina:   /* might work if TestLegality is off */
904       case VariantCapaRandom: /* should work */
905       case VariantJanus:      /* should work */
906       case VariantSuper:      /* experimental */
907       case VariantGreat:      /* experimental, requires legality testing to be off */
908         break;
909       }
910     }
911
912     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
913     InitEngineUCI( installDir, &second );
914 }
915
916 int NextIntegerFromString( char ** str, long * value )
917 {
918     int result = -1;
919     char * s = *str;
920
921     while( *s == ' ' || *s == '\t' ) {
922         s++;
923     }
924
925     *value = 0;
926
927     if( *s >= '0' && *s <= '9' ) {
928         while( *s >= '0' && *s <= '9' ) {
929             *value = *value * 10 + (*s - '0');
930             s++;
931         }
932
933         result = 0;
934     }
935
936     *str = s;
937
938     return result;
939 }
940
941 int NextTimeControlFromString( char ** str, long * value )
942 {
943     long temp;
944     int result = NextIntegerFromString( str, &temp );
945
946     if( result == 0 ) {
947         *value = temp * 60; /* Minutes */
948         if( **str == ':' ) {
949             (*str)++;
950             result = NextIntegerFromString( str, &temp );
951             *value += temp; /* Seconds */
952         }
953     }
954
955     return result;
956 }
957
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
960     int result = -1; long temp, temp2;
961
962     if(**str != '+') return -1; // old params remain in force!
963     (*str)++;
964     if( NextTimeControlFromString( str, &temp ) ) return -1;
965
966     if(**str != '/') {
967         /* time only: incremental or sudden-death time control */
968         if(**str == '+') { /* increment follows; read it */
969             (*str)++;
970             if(result = NextIntegerFromString( str, &temp2)) return -1;
971             *inc = temp2 * 1000;
972         } else *inc = 0;
973         *moves = 0; *tc = temp * 1000; 
974         return 0;
975     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
976
977     (*str)++; /* classical time control */
978     result = NextTimeControlFromString( str, &temp2);
979     if(result == 0) {
980         *moves = temp/60;
981         *tc    = temp2 * 1000;
982         *inc   = 0;
983     }
984     return result;
985 }
986
987 int GetTimeQuota(int movenr)
988 {   /* [HGM] get time to add from the multi-session time-control string */
989     int moves=1; /* kludge to force reading of first session */
990     long time, increment;
991     char *s = fullTimeControlString;
992
993     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994     do {
995         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997         if(movenr == -1) return time;    /* last move before new session     */
998         if(!moves) return increment;     /* current session is incremental   */
999         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000     } while(movenr >= -1);               /* try again for next session       */
1001
1002     return 0; // no new time quota on this move
1003 }
1004
1005 int
1006 ParseTimeControl(tc, ti, mps)
1007      char *tc;
1008      int ti;
1009      int mps;
1010 {
1011   long tc1;
1012   long tc2;
1013   char buf[MSG_SIZ];
1014   
1015   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1016   if(ti > 0) {
1017     if(mps)
1018       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019     else sprintf(buf, "+%s+%d", tc, ti);
1020   } else {
1021     if(mps)
1022              sprintf(buf, "+%d/%s", mps, tc);
1023     else sprintf(buf, "+%s", tc);
1024   }
1025   fullTimeControlString = StrSave(buf);
1026   
1027   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1028     return FALSE;
1029   }
1030   
1031   if( *tc == '/' ) {
1032     /* Parse second time control */
1033     tc++;
1034     
1035     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1036       return FALSE;
1037     }
1038     
1039     if( tc2 == 0 ) {
1040       return FALSE;
1041     }
1042     
1043     timeControl_2 = tc2 * 1000;
1044   }
1045   else {
1046     timeControl_2 = 0;
1047   }
1048   
1049   if( tc1 == 0 ) {
1050     return FALSE;
1051   }
1052   
1053   timeControl = tc1 * 1000;
1054   
1055   if (ti >= 0) {
1056     timeIncrement = ti * 1000;  /* convert to ms */
1057     movesPerSession = 0;
1058   } else {
1059     timeIncrement = 0;
1060     movesPerSession = mps;
1061   }
1062   return TRUE;
1063 }
1064
1065 void
1066 InitBackEnd2()
1067 {
1068     if (appData.debugMode) {
1069         fprintf(debugFP, "%s\n", programVersion);
1070     }
1071
1072     set_cont_sequence(appData.wrapContSeq);
1073     if (appData.matchGames > 0) {
1074         appData.matchMode = TRUE;
1075     } else if (appData.matchMode) {
1076         appData.matchGames = 1;
1077     }
1078     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079         appData.matchGames = appData.sameColorGames;
1080     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1083     }
1084     Reset(TRUE, FALSE);
1085     if (appData.noChessProgram || first.protocolVersion == 1) {
1086       InitBackEnd3();
1087     } else {
1088       /* kludge: allow timeout for initial "feature" commands */
1089       FreezeUI();
1090       DisplayMessage("", _("Starting chess program"));
1091       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1092     }
1093 }
1094
1095 void
1096 InitBackEnd3 P((void))
1097 {
1098     GameMode initialMode;
1099     char buf[MSG_SIZ];
1100     int err;
1101
1102     InitChessProgram(&first, startedFromSetupPosition);
1103
1104
1105     if (appData.icsActive) {
1106 #ifdef WIN32
1107         /* [DM] Make a console window if needed [HGM] merged ifs */
1108         ConsoleCreate(); 
1109 #endif
1110         err = establish();
1111         if (err != 0) {
1112             if (*appData.icsCommPort != NULLCHAR) {
1113                 sprintf(buf, _("Could not open comm port %s"),  
1114                         appData.icsCommPort);
1115             } else {
1116                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1117                         appData.icsHost, appData.icsPort);
1118             }
1119             DisplayFatalError(buf, err, 1);
1120             return;
1121         }
1122         SetICSMode();
1123         telnetISR =
1124           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125         fromUserISR =
1126           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129     } else if (appData.noChessProgram) {
1130         SetNCPMode();
1131     } else {
1132         SetGNUMode();
1133     }
1134
1135     if (*appData.cmailGameName != NULLCHAR) {
1136         SetCmailMode();
1137         OpenLoopback(&cmailPR);
1138         cmailISR =
1139           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1140     }
1141     
1142     ThawUI();
1143     DisplayMessage("", "");
1144     if (StrCaseCmp(appData.initialMode, "") == 0) {
1145       initialMode = BeginningOfGame;
1146     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147       initialMode = TwoMachinesPlay;
1148     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149       initialMode = AnalyzeFile; 
1150     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151       initialMode = AnalyzeMode;
1152     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153       initialMode = MachinePlaysWhite;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155       initialMode = MachinePlaysBlack;
1156     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157       initialMode = EditGame;
1158     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159       initialMode = EditPosition;
1160     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161       initialMode = Training;
1162     } else {
1163       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164       DisplayFatalError(buf, 0, 2);
1165       return;
1166     }
1167
1168     if (appData.matchMode) {
1169         /* Set up machine vs. machine match */
1170         if (appData.noChessProgram) {
1171             DisplayFatalError(_("Can't have a match with no chess programs"),
1172                               0, 2);
1173             return;
1174         }
1175         matchMode = TRUE;
1176         matchGame = 1;
1177         if (*appData.loadGameFile != NULLCHAR) {
1178             int index = appData.loadGameIndex; // [HGM] autoinc
1179             if(index<0) lastIndex = index = 1;
1180             if (!LoadGameFromFile(appData.loadGameFile,
1181                                   index,
1182                                   appData.loadGameFile, FALSE)) {
1183                 DisplayFatalError(_("Bad game file"), 0, 1);
1184                 return;
1185             }
1186         } else if (*appData.loadPositionFile != NULLCHAR) {
1187             int index = appData.loadPositionIndex; // [HGM] autoinc
1188             if(index<0) lastIndex = index = 1;
1189             if (!LoadPositionFromFile(appData.loadPositionFile,
1190                                       index,
1191                                       appData.loadPositionFile)) {
1192                 DisplayFatalError(_("Bad position file"), 0, 1);
1193                 return;
1194             }
1195         }
1196         TwoMachinesEvent();
1197     } else if (*appData.cmailGameName != NULLCHAR) {
1198         /* Set up cmail mode */
1199         ReloadCmailMsgEvent(TRUE);
1200     } else {
1201         /* Set up other modes */
1202         if (initialMode == AnalyzeFile) {
1203           if (*appData.loadGameFile == NULLCHAR) {
1204             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1205             return;
1206           }
1207         }
1208         if (*appData.loadGameFile != NULLCHAR) {
1209             (void) LoadGameFromFile(appData.loadGameFile,
1210                                     appData.loadGameIndex,
1211                                     appData.loadGameFile, TRUE);
1212         } else if (*appData.loadPositionFile != NULLCHAR) {
1213             (void) LoadPositionFromFile(appData.loadPositionFile,
1214                                         appData.loadPositionIndex,
1215                                         appData.loadPositionFile);
1216             /* [HGM] try to make self-starting even after FEN load */
1217             /* to allow automatic setup of fairy variants with wtm */
1218             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219                 gameMode = BeginningOfGame;
1220                 setboardSpoiledMachineBlack = 1;
1221             }
1222             /* [HGM] loadPos: make that every new game uses the setup */
1223             /* from file as long as we do not switch variant          */
1224             if(!blackPlaysFirst) {
1225                 startedFromPositionFile = TRUE;
1226                 CopyBoard(filePosition, boards[0]);
1227             }
1228         }
1229         if (initialMode == AnalyzeMode) {
1230           if (appData.noChessProgram) {
1231             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1232             return;
1233           }
1234           if (appData.icsActive) {
1235             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1236             return;
1237           }
1238           AnalyzeModeEvent();
1239         } else if (initialMode == AnalyzeFile) {
1240           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241           ShowThinkingEvent();
1242           AnalyzeFileEvent();
1243           AnalysisPeriodicEvent(1);
1244         } else if (initialMode == MachinePlaysWhite) {
1245           if (appData.noChessProgram) {
1246             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1247                               0, 2);
1248             return;
1249           }
1250           if (appData.icsActive) {
1251             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1252                               0, 2);
1253             return;
1254           }
1255           MachineWhiteEvent();
1256         } else if (initialMode == MachinePlaysBlack) {
1257           if (appData.noChessProgram) {
1258             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1259                               0, 2);
1260             return;
1261           }
1262           if (appData.icsActive) {
1263             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1264                               0, 2);
1265             return;
1266           }
1267           MachineBlackEvent();
1268         } else if (initialMode == TwoMachinesPlay) {
1269           if (appData.noChessProgram) {
1270             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1271                               0, 2);
1272             return;
1273           }
1274           if (appData.icsActive) {
1275             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1276                               0, 2);
1277             return;
1278           }
1279           TwoMachinesEvent();
1280         } else if (initialMode == EditGame) {
1281           EditGameEvent();
1282         } else if (initialMode == EditPosition) {
1283           EditPositionEvent();
1284         } else if (initialMode == Training) {
1285           if (*appData.loadGameFile == NULLCHAR) {
1286             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1287             return;
1288           }
1289           TrainingEvent();
1290         }
1291     }
1292 }
1293
1294 /*
1295  * Establish will establish a contact to a remote host.port.
1296  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297  *  used to talk to the host.
1298  * Returns 0 if okay, error code if not.
1299  */
1300 int
1301 establish()
1302 {
1303     char buf[MSG_SIZ];
1304
1305     if (*appData.icsCommPort != NULLCHAR) {
1306         /* Talk to the host through a serial comm port */
1307         return OpenCommPort(appData.icsCommPort, &icsPR);
1308
1309     } else if (*appData.gateway != NULLCHAR) {
1310         if (*appData.remoteShell == NULLCHAR) {
1311             /* Use the rcmd protocol to run telnet program on a gateway host */
1312             snprintf(buf, sizeof(buf), "%s %s %s",
1313                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1314             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1315
1316         } else {
1317             /* Use the rsh program to run telnet program on a gateway host */
1318             if (*appData.remoteUser == NULLCHAR) {
1319                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320                         appData.gateway, appData.telnetProgram,
1321                         appData.icsHost, appData.icsPort);
1322             } else {
1323                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324                         appData.remoteShell, appData.gateway, 
1325                         appData.remoteUser, appData.telnetProgram,
1326                         appData.icsHost, appData.icsPort);
1327             }
1328             return StartChildProcess(buf, "", &icsPR);
1329
1330         }
1331     } else if (appData.useTelnet) {
1332         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1333
1334     } else {
1335         /* TCP socket interface differs somewhat between
1336            Unix and NT; handle details in the front end.
1337            */
1338         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1339     }
1340 }
1341
1342 void
1343 show_bytes(fp, buf, count)
1344      FILE *fp;
1345      char *buf;
1346      int count;
1347 {
1348     while (count--) {
1349         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350             fprintf(fp, "\\%03o", *buf & 0xff);
1351         } else {
1352             putc(*buf, fp);
1353         }
1354         buf++;
1355     }
1356     fflush(fp);
1357 }
1358
1359 /* Returns an errno value */
1360 int
1361 OutputMaybeTelnet(pr, message, count, outError)
1362      ProcRef pr;
1363      char *message;
1364      int count;
1365      int *outError;
1366 {
1367     char buf[8192], *p, *q, *buflim;
1368     int left, newcount, outcount;
1369
1370     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371         *appData.gateway != NULLCHAR) {
1372         if (appData.debugMode) {
1373             fprintf(debugFP, ">ICS: ");
1374             show_bytes(debugFP, message, count);
1375             fprintf(debugFP, "\n");
1376         }
1377         return OutputToProcess(pr, message, count, outError);
1378     }
1379
1380     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1381     p = message;
1382     q = buf;
1383     left = count;
1384     newcount = 0;
1385     while (left) {
1386         if (q >= buflim) {
1387             if (appData.debugMode) {
1388                 fprintf(debugFP, ">ICS: ");
1389                 show_bytes(debugFP, buf, newcount);
1390                 fprintf(debugFP, "\n");
1391             }
1392             outcount = OutputToProcess(pr, buf, newcount, outError);
1393             if (outcount < newcount) return -1; /* to be sure */
1394             q = buf;
1395             newcount = 0;
1396         }
1397         if (*p == '\n') {
1398             *q++ = '\r';
1399             newcount++;
1400         } else if (((unsigned char) *p) == TN_IAC) {
1401             *q++ = (char) TN_IAC;
1402             newcount ++;
1403         }
1404         *q++ = *p++;
1405         newcount++;
1406         left--;
1407     }
1408     if (appData.debugMode) {
1409         fprintf(debugFP, ">ICS: ");
1410         show_bytes(debugFP, buf, newcount);
1411         fprintf(debugFP, "\n");
1412     }
1413     outcount = OutputToProcess(pr, buf, newcount, outError);
1414     if (outcount < newcount) return -1; /* to be sure */
1415     return count;
1416 }
1417
1418 void
1419 read_from_player(isr, closure, message, count, error)
1420      InputSourceRef isr;
1421      VOIDSTAR closure;
1422      char *message;
1423      int count;
1424      int error;
1425 {
1426     int outError, outCount;
1427     static int gotEof = 0;
1428
1429     /* Pass data read from player on to ICS */
1430     if (count > 0) {
1431         gotEof = 0;
1432         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433         if (outCount < count) {
1434             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435         }
1436     } else if (count < 0) {
1437         RemoveInputSource(isr);
1438         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439     } else if (gotEof++ > 0) {
1440         RemoveInputSource(isr);
1441         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1442     }
1443 }
1444
1445 void
1446 KeepAlive()
1447 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450     SendToICS("date\n");
1451     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1452 }
1453
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1456 {
1457     char buffer[MSG_SIZ];
1458     va_list args;
1459
1460     va_start(args, format);
1461     vsnprintf(buffer, sizeof(buffer), format, args);
1462     buffer[sizeof(buffer)-1] = '\0';
1463     SendToICS(buffer);
1464     va_end(args);
1465 }
1466
1467 void
1468 SendToICS(s)
1469      char *s;
1470 {
1471     int count, outCount, outError;
1472
1473     if (icsPR == NULL) return;
1474
1475     count = strlen(s);
1476     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477     if (outCount < count) {
1478         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479     }
1480 }
1481
1482 /* This is used for sending logon scripts to the ICS. Sending
1483    without a delay causes problems when using timestamp on ICC
1484    (at least on my machine). */
1485 void
1486 SendToICSDelayed(s,msdelay)
1487      char *s;
1488      long msdelay;
1489 {
1490     int count, outCount, outError;
1491
1492     if (icsPR == NULL) return;
1493
1494     count = strlen(s);
1495     if (appData.debugMode) {
1496         fprintf(debugFP, ">ICS: ");
1497         show_bytes(debugFP, s, count);
1498         fprintf(debugFP, "\n");
1499     }
1500     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501                                       msdelay);
1502     if (outCount < count) {
1503         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504     }
1505 }
1506
1507
1508 /* Remove all highlighting escape sequences in s
1509    Also deletes any suffix starting with '(' 
1510    */
1511 char *
1512 StripHighlightAndTitle(s)
1513      char *s;
1514 {
1515     static char retbuf[MSG_SIZ];
1516     char *p = retbuf;
1517
1518     while (*s != NULLCHAR) {
1519         while (*s == '\033') {
1520             while (*s != NULLCHAR && !isalpha(*s)) s++;
1521             if (*s != NULLCHAR) s++;
1522         }
1523         while (*s != NULLCHAR && *s != '\033') {
1524             if (*s == '(' || *s == '[') {
1525                 *p = NULLCHAR;
1526                 return retbuf;
1527             }
1528             *p++ = *s++;
1529         }
1530     }
1531     *p = NULLCHAR;
1532     return retbuf;
1533 }
1534
1535 /* Remove all highlighting escape sequences in s */
1536 char *
1537 StripHighlight(s)
1538      char *s;
1539 {
1540     static char retbuf[MSG_SIZ];
1541     char *p = retbuf;
1542
1543     while (*s != NULLCHAR) {
1544         while (*s == '\033') {
1545             while (*s != NULLCHAR && !isalpha(*s)) s++;
1546             if (*s != NULLCHAR) s++;
1547         }
1548         while (*s != NULLCHAR && *s != '\033') {
1549             *p++ = *s++;
1550         }
1551     }
1552     *p = NULLCHAR;
1553     return retbuf;
1554 }
1555
1556 char *variantNames[] = VARIANT_NAMES;
1557 char *
1558 VariantName(v)
1559      VariantClass v;
1560 {
1561     return variantNames[v];
1562 }
1563
1564
1565 /* Identify a variant from the strings the chess servers use or the
1566    PGN Variant tag names we use. */
1567 VariantClass
1568 StringToVariant(e)
1569      char *e;
1570 {
1571     char *p;
1572     int wnum = -1;
1573     VariantClass v = VariantNormal;
1574     int i, found = FALSE;
1575     char buf[MSG_SIZ];
1576
1577     if (!e) return v;
1578
1579     /* [HGM] skip over optional board-size prefixes */
1580     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582         while( *e++ != '_');
1583     }
1584
1585     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1586         v = VariantNormal;
1587         found = TRUE;
1588     } else
1589     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590       if (StrCaseStr(e, variantNames[i])) {
1591         v = (VariantClass) i;
1592         found = TRUE;
1593         break;
1594       }
1595     }
1596
1597     if (!found) {
1598       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599           || StrCaseStr(e, "wild/fr") 
1600           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601         v = VariantFischeRandom;
1602       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603                  (i = 1, p = StrCaseStr(e, "w"))) {
1604         p += i;
1605         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606         if (isdigit(*p)) {
1607           wnum = atoi(p);
1608         } else {
1609           wnum = -1;
1610         }
1611         switch (wnum) {
1612         case 0: /* FICS only, actually */
1613         case 1:
1614           /* Castling legal even if K starts on d-file */
1615           v = VariantWildCastle;
1616           break;
1617         case 2:
1618         case 3:
1619         case 4:
1620           /* Castling illegal even if K & R happen to start in
1621              normal positions. */
1622           v = VariantNoCastle;
1623           break;
1624         case 5:
1625         case 7:
1626         case 8:
1627         case 10:
1628         case 11:
1629         case 12:
1630         case 13:
1631         case 14:
1632         case 15:
1633         case 18:
1634         case 19:
1635           /* Castling legal iff K & R start in normal positions */
1636           v = VariantNormal;
1637           break;
1638         case 6:
1639         case 20:
1640         case 21:
1641           /* Special wilds for position setup; unclear what to do here */
1642           v = VariantLoadable;
1643           break;
1644         case 9:
1645           /* Bizarre ICC game */
1646           v = VariantTwoKings;
1647           break;
1648         case 16:
1649           v = VariantKriegspiel;
1650           break;
1651         case 17:
1652           v = VariantLosers;
1653           break;
1654         case 22:
1655           v = VariantFischeRandom;
1656           break;
1657         case 23:
1658           v = VariantCrazyhouse;
1659           break;
1660         case 24:
1661           v = VariantBughouse;
1662           break;
1663         case 25:
1664           v = Variant3Check;
1665           break;
1666         case 26:
1667           /* Not quite the same as FICS suicide! */
1668           v = VariantGiveaway;
1669           break;
1670         case 27:
1671           v = VariantAtomic;
1672           break;
1673         case 28:
1674           v = VariantShatranj;
1675           break;
1676
1677         /* Temporary names for future ICC types.  The name *will* change in 
1678            the next xboard/WinBoard release after ICC defines it. */
1679         case 29:
1680           v = Variant29;
1681           break;
1682         case 30:
1683           v = Variant30;
1684           break;
1685         case 31:
1686           v = Variant31;
1687           break;
1688         case 32:
1689           v = Variant32;
1690           break;
1691         case 33:
1692           v = Variant33;
1693           break;
1694         case 34:
1695           v = Variant34;
1696           break;
1697         case 35:
1698           v = Variant35;
1699           break;
1700         case 36:
1701           v = Variant36;
1702           break;
1703         case 37:
1704           v = VariantShogi;
1705           break;
1706         case 38:
1707           v = VariantXiangqi;
1708           break;
1709         case 39:
1710           v = VariantCourier;
1711           break;
1712         case 40:
1713           v = VariantGothic;
1714           break;
1715         case 41:
1716           v = VariantCapablanca;
1717           break;
1718         case 42:
1719           v = VariantKnightmate;
1720           break;
1721         case 43:
1722           v = VariantFairy;
1723           break;
1724         case 44:
1725           v = VariantCylinder;
1726           break;
1727         case 45:
1728           v = VariantFalcon;
1729           break;
1730         case 46:
1731           v = VariantCapaRandom;
1732           break;
1733         case 47:
1734           v = VariantBerolina;
1735           break;
1736         case 48:
1737           v = VariantJanus;
1738           break;
1739         case 49:
1740           v = VariantSuper;
1741           break;
1742         case 50:
1743           v = VariantGreat;
1744           break;
1745         case -1:
1746           /* Found "wild" or "w" in the string but no number;
1747              must assume it's normal chess. */
1748           v = VariantNormal;
1749           break;
1750         default:
1751           sprintf(buf, _("Unknown wild type %d"), wnum);
1752           DisplayError(buf, 0);
1753           v = VariantUnknown;
1754           break;
1755         }
1756       }
1757     }
1758     if (appData.debugMode) {
1759       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760               e, wnum, VariantName(v));
1761     }
1762     return v;
1763 }
1764
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1767
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769    advance *index beyond it, and set leftover_start to the new value of
1770    *index; else return FALSE.  If pattern contains the character '*', it
1771    matches any sequence of characters not containing '\r', '\n', or the
1772    character following the '*' (if any), and the matched sequence(s) are
1773    copied into star_match.
1774    */
1775 int
1776 looking_at(buf, index, pattern)
1777      char *buf;
1778      int *index;
1779      char *pattern;
1780 {
1781     char *bufp = &buf[*index], *patternp = pattern;
1782     int star_count = 0;
1783     char *matchp = star_match[0];
1784     
1785     for (;;) {
1786         if (*patternp == NULLCHAR) {
1787             *index = leftover_start = bufp - buf;
1788             *matchp = NULLCHAR;
1789             return TRUE;
1790         }
1791         if (*bufp == NULLCHAR) return FALSE;
1792         if (*patternp == '*') {
1793             if (*bufp == *(patternp + 1)) {
1794                 *matchp = NULLCHAR;
1795                 matchp = star_match[++star_count];
1796                 patternp += 2;
1797                 bufp++;
1798                 continue;
1799             } else if (*bufp == '\n' || *bufp == '\r') {
1800                 patternp++;
1801                 if (*patternp == NULLCHAR)
1802                   continue;
1803                 else
1804                   return FALSE;
1805             } else {
1806                 *matchp++ = *bufp++;
1807                 continue;
1808             }
1809         }
1810         if (*patternp != *bufp) return FALSE;
1811         patternp++;
1812         bufp++;
1813     }
1814 }
1815
1816 void
1817 SendToPlayer(data, length)
1818      char *data;
1819      int length;
1820 {
1821     int error, outCount;
1822     outCount = OutputToProcess(NoProc, data, length, &error);
1823     if (outCount < length) {
1824         DisplayFatalError(_("Error writing to display"), error, 1);
1825     }
1826 }
1827
1828 void
1829 PackHolding(packed, holding)
1830      char packed[];
1831      char *holding;
1832 {
1833     char *p = holding;
1834     char *q = packed;
1835     int runlength = 0;
1836     int curr = 9999;
1837     do {
1838         if (*p == curr) {
1839             runlength++;
1840         } else {
1841             switch (runlength) {
1842               case 0:
1843                 break;
1844               case 1:
1845                 *q++ = curr;
1846                 break;
1847               case 2:
1848                 *q++ = curr;
1849                 *q++ = curr;
1850                 break;
1851               default:
1852                 sprintf(q, "%d", runlength);
1853                 while (*q) q++;
1854                 *q++ = curr;
1855                 break;
1856             }
1857             runlength = 1;
1858             curr = *p;
1859         }
1860     } while (*p++);
1861     *q = NULLCHAR;
1862 }
1863
1864 /* Telnet protocol requests from the front end */
1865 void
1866 TelnetRequest(ddww, option)
1867      unsigned char ddww, option;
1868 {
1869     unsigned char msg[3];
1870     int outCount, outError;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873
1874     if (appData.debugMode) {
1875         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1876         switch (ddww) {
1877           case TN_DO:
1878             ddwwStr = "DO";
1879             break;
1880           case TN_DONT:
1881             ddwwStr = "DONT";
1882             break;
1883           case TN_WILL:
1884             ddwwStr = "WILL";
1885             break;
1886           case TN_WONT:
1887             ddwwStr = "WONT";
1888             break;
1889           default:
1890             ddwwStr = buf1;
1891             sprintf(buf1, "%d", ddww);
1892             break;
1893         }
1894         switch (option) {
1895           case TN_ECHO:
1896             optionStr = "ECHO";
1897             break;
1898           default:
1899             optionStr = buf2;
1900             sprintf(buf2, "%d", option);
1901             break;
1902         }
1903         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1904     }
1905     msg[0] = TN_IAC;
1906     msg[1] = ddww;
1907     msg[2] = option;
1908     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909     if (outCount < 3) {
1910         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911     }
1912 }
1913
1914 void
1915 DoEcho()
1916 {
1917     if (!appData.icsActive) return;
1918     TelnetRequest(TN_DO, TN_ECHO);
1919 }
1920
1921 void
1922 DontEcho()
1923 {
1924     if (!appData.icsActive) return;
1925     TelnetRequest(TN_DONT, TN_ECHO);
1926 }
1927
1928 void
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 {
1931     /* put the holdings sent to us by the server on the board holdings area */
1932     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1933     char p;
1934     ChessSquare piece;
1935
1936     if(gameInfo.holdingsWidth < 2)  return;
1937     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938         return; // prevent overwriting by pre-board holdings
1939
1940     if( (int)lowestPiece >= BlackPawn ) {
1941         holdingsColumn = 0;
1942         countsColumn = 1;
1943         holdingsStartRow = BOARD_HEIGHT-1;
1944         direction = -1;
1945     } else {
1946         holdingsColumn = BOARD_WIDTH-1;
1947         countsColumn = BOARD_WIDTH-2;
1948         holdingsStartRow = 0;
1949         direction = 1;
1950     }
1951
1952     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953         board[i][holdingsColumn] = EmptySquare;
1954         board[i][countsColumn]   = (ChessSquare) 0;
1955     }
1956     while( (p=*holdings++) != NULLCHAR ) {
1957         piece = CharToPiece( ToUpper(p) );
1958         if(piece == EmptySquare) continue;
1959         /*j = (int) piece - (int) WhitePawn;*/
1960         j = PieceToNumber(piece);
1961         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962         if(j < 0) continue;               /* should not happen */
1963         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965         board[holdingsStartRow+j*direction][countsColumn]++;
1966     }
1967 }
1968
1969
1970 void
1971 VariantSwitch(Board board, VariantClass newVariant)
1972 {
1973    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1974    Board oldBoard;
1975
1976    startedFromPositionFile = FALSE;
1977    if(gameInfo.variant == newVariant) return;
1978
1979    /* [HGM] This routine is called each time an assignment is made to
1980     * gameInfo.variant during a game, to make sure the board sizes
1981     * are set to match the new variant. If that means adding or deleting
1982     * holdings, we shift the playing board accordingly
1983     * This kludge is needed because in ICS observe mode, we get boards
1984     * of an ongoing game without knowing the variant, and learn about the
1985     * latter only later. This can be because of the move list we requested,
1986     * in which case the game history is refilled from the beginning anyway,
1987     * but also when receiving holdings of a crazyhouse game. In the latter
1988     * case we want to add those holdings to the already received position.
1989     */
1990
1991    
1992    if (appData.debugMode) {
1993      fprintf(debugFP, "Switch board from %s to %s\n",
1994              VariantName(gameInfo.variant), VariantName(newVariant));
1995      setbuf(debugFP, NULL);
1996    }
1997    shuffleOpenings = 0;       /* [HGM] shuffle */
1998    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1999    switch(newVariant) 
2000      {
2001      case VariantShogi:
2002        newWidth = 9;  newHeight = 9;
2003        gameInfo.holdingsSize = 7;
2004      case VariantBughouse:
2005      case VariantCrazyhouse:
2006        newHoldingsWidth = 2; break;
2007      case VariantGreat:
2008        newWidth = 10;
2009      case VariantSuper:
2010        newHoldingsWidth = 2;
2011        gameInfo.holdingsSize = 8;
2012        break;
2013      case VariantGothic:
2014      case VariantCapablanca:
2015      case VariantCapaRandom:
2016        newWidth = 10;
2017      default:
2018        newHoldingsWidth = gameInfo.holdingsSize = 0;
2019      };
2020    
2021    if(newWidth  != gameInfo.boardWidth  ||
2022       newHeight != gameInfo.boardHeight ||
2023       newHoldingsWidth != gameInfo.holdingsWidth ) {
2024      
2025      /* shift position to new playing area, if needed */
2026      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027        for(i=0; i<BOARD_HEIGHT; i++) 
2028          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030              board[i][j];
2031        for(i=0; i<newHeight; i++) {
2032          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034        }
2035      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036        for(i=0; i<BOARD_HEIGHT; i++)
2037          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2039              board[i][j];
2040      }
2041      gameInfo.boardWidth  = newWidth;
2042      gameInfo.boardHeight = newHeight;
2043      gameInfo.holdingsWidth = newHoldingsWidth;
2044      gameInfo.variant = newVariant;
2045      InitDrawingSizes(-2, 0);
2046    } else gameInfo.variant = newVariant;
2047    CopyBoard(oldBoard, board);   // remember correctly formatted board
2048      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2049    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2050 }
2051
2052 static int loggedOn = FALSE;
2053
2054 /*-- Game start info cache: --*/
2055 int gs_gamenum;
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\   ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2063
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2066
2067 // [HGM] seekgraph
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 char *seekAdList[MAX_SEEK_ADS];
2072 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2073 float tcList[MAX_SEEK_ADS];
2074 char colorList[MAX_SEEK_ADS];
2075 int nrOfSeekAds = 0;
2076 int minRating = 1010, maxRating = 2800;
2077 int hMargin = 10, vMargin = 20, h, w;
2078 extern int squareSize, lineGap;
2079
2080 void
2081 PlotSeekAd(int i)
2082 {
2083         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2084         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2085         zList[i] = 0;
2086         if(r < minRating+100 && r >=0 ) r = minRating+100;
2087         if(r > maxRating) r = maxRating;
2088         if(tc < 1.) tc = 1.;
2089         if(tc > 95.) tc = 95.;
2090         x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2091         y = ((double)r - minRating)/(maxRating - minRating)
2092             * (h-vMargin-squareSize/8-1) + vMargin;
2093         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2094         if(strstr(seekAdList[i], " u ")) color = 1;
2095         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2096            !strstr(seekAdList[i], "bullet") &&
2097            !strstr(seekAdList[i], "blitz") &&
2098            !strstr(seekAdList[i], "standard") ) color = 2;
2099         DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, colorList[i]=color);
2100 }
2101
2102 void
2103 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2104 {
2105         char buf[MSG_SIZ], *ext = "";
2106         VariantClass v = StringToVariant(type);
2107         if(strstr(type, "wild")) {
2108             ext = type + 4; // append wild number
2109             if(v == VariantFischeRandom) type = "chess960"; else
2110             if(v == VariantLoadable) type = "setup"; else
2111             type = VariantName(v);
2112         }
2113         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2114         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2115             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2116             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2117             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2118             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2119             seekNrList[nrOfSeekAds] = nr;
2120             seekAdList[nrOfSeekAds++] = StrSave(buf);
2121             if(plot) PlotSeekAd(nrOfSeekAds-1);
2122         }
2123 }
2124
2125 void
2126 EraseSeekDot(int i)
2127 {
2128     int x = xList[i], y = yList[i], d=squareSize/4, k;
2129     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2130     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2131     // now replot every dot that overlapped
2132     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2133         int xx = xList[k], yy = yList[k];
2134         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2135             DrawSeekDot(xx, yy, colorList[k]);
2136     }
2137 }
2138
2139 void
2140 RemoveSeekAd(int nr)
2141 {
2142         int i;
2143         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2144             EraseSeekDot(i);
2145             if(seekAdList[i]) free(seekAdList[i]);
2146             seekAdList[i] = seekAdList[--nrOfSeekAds];
2147             seekNrList[i] = seekNrList[nrOfSeekAds];
2148             ratingList[i] = ratingList[nrOfSeekAds];
2149             colorList[i]  = colorList[nrOfSeekAds];
2150             tcList[i] = tcList[nrOfSeekAds];
2151             xList[i]  = xList[nrOfSeekAds];
2152             yList[i]  = yList[nrOfSeekAds];
2153             zList[i]  = zList[nrOfSeekAds];
2154             seekAdList[nrOfSeekAds] = NULL;
2155             break;
2156         }
2157 }
2158
2159 Boolean
2160 MatchSoughtLine(char *line)
2161 {
2162     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2163     int nr, base, inc, u=0; char dummy;
2164
2165     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2166        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2167        (u=1) &&
2168        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2169         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2170         // match: compact and save the line
2171         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2172         return TRUE;
2173     }
2174     return FALSE;
2175 }
2176
2177 int
2178 DrawSeekGraph()
2179 {
2180     if(!seekGraphUp) return FALSE;
2181     int i;
2182     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2183     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2184
2185     DrawSeekBackground(0, 0, w, h);
2186     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2187     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2188     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2189         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2190         yy = h-1-yy;
2191         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2192         if(i%500 == 0) {
2193             char buf[MSG_SIZ];
2194             sprintf(buf, "%d", i);
2195             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2196         }
2197     }
2198     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2199     for(i=1; i<100; i+=(i<10?1:5)) {
2200         int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2201         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2202         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2203             char buf[MSG_SIZ];
2204             sprintf(buf, "%d", i);
2205             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2206         }
2207     }
2208     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2209     return TRUE;
2210 }
2211
2212 int SeekGraphClick(ClickType click, int x, int y, Boolean moving)
2213 {
2214     static int lastDown = 0;
2215     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2216         if(click == Release || moving) return FALSE;
2217         nrOfSeekAds = 0;
2218         soughtPending = TRUE;
2219         SendToICS(ics_prefix);
2220         SendToICS("sought\n"); // should this be "sought all"?
2221     } else { // issue challenge based on clicked ad
2222         int dist = 10000; int i, closest = 0;
2223         for(i=0; i<nrOfSeekAds; i++) {
2224             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2225             if(d < dist) { dist = d; closest = i; }
2226             if(click == Press && zList[i]>0) zList[i] *= 0.8; // age priority
2227         }
2228         if(dist < 300) {
2229             char buf[MSG_SIZ];
2230             if(lastDown != closest) DisplayMessage(seekAdList[closest], "");
2231             sprintf(buf, "play %d\n", seekNrList[closest]);
2232             if(click == Press) { lastDown = closest; return TRUE; } // on press 'hit', only show info
2233             SendToICS(ics_prefix);
2234             SendToICS(buf); // should this be "sought all"?
2235         } else if(click == Release) { // release 'miss' is ignored
2236             zList[lastDown] = 200; // make future selection of the rejected ad more difficult
2237             return TRUE;
2238         } else if(moving) { if(lastDown >= 0) DisplayMessage("", ""); lastDown = -1; return TRUE; }
2239         // press miss or release hit 'pop down' seek graph
2240         seekGraphUp = FALSE;
2241         DrawPosition(TRUE, NULL);
2242     }
2243     return TRUE;
2244 }
2245
2246 void
2247 read_from_ics(isr, closure, data, count, error)
2248      InputSourceRef isr;
2249      VOIDSTAR closure;
2250      char *data;
2251      int count;
2252      int error;
2253 {
2254 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2255 #define STARTED_NONE 0
2256 #define STARTED_MOVES 1
2257 #define STARTED_BOARD 2
2258 #define STARTED_OBSERVE 3
2259 #define STARTED_HOLDINGS 4
2260 #define STARTED_CHATTER 5
2261 #define STARTED_COMMENT 6
2262 #define STARTED_MOVES_NOHIDE 7
2263     
2264     static int started = STARTED_NONE;
2265     static char parse[20000];
2266     static int parse_pos = 0;
2267     static char buf[BUF_SIZE + 1];
2268     static int firstTime = TRUE, intfSet = FALSE;
2269     static ColorClass prevColor = ColorNormal;
2270     static int savingComment = FALSE;
2271     static int cmatch = 0; // continuation sequence match
2272     char *bp;
2273     char str[500];
2274     int i, oldi;
2275     int buf_len;
2276     int next_out;
2277     int tkind;
2278     int backup;    /* [DM] For zippy color lines */
2279     char *p;
2280     char talker[MSG_SIZ]; // [HGM] chat
2281     int channel;
2282
2283     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2284
2285     if (appData.debugMode) {
2286       if (!error) {
2287         fprintf(debugFP, "<ICS: ");
2288         show_bytes(debugFP, data, count);
2289         fprintf(debugFP, "\n");
2290       }
2291     }
2292
2293     if (appData.debugMode) { int f = forwardMostMove;
2294         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2295                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2296                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2297     }
2298     if (count > 0) {
2299         /* If last read ended with a partial line that we couldn't parse,
2300            prepend it to the new read and try again. */
2301         if (leftover_len > 0) {
2302             for (i=0; i<leftover_len; i++)
2303               buf[i] = buf[leftover_start + i];
2304         }
2305
2306     /* copy new characters into the buffer */
2307     bp = buf + leftover_len;
2308     buf_len=leftover_len;
2309     for (i=0; i<count; i++)
2310     {
2311         // ignore these
2312         if (data[i] == '\r')
2313             continue;
2314
2315         // join lines split by ICS?
2316         if (!appData.noJoin)
2317         {
2318             /*
2319                 Joining just consists of finding matches against the
2320                 continuation sequence, and discarding that sequence
2321                 if found instead of copying it.  So, until a match
2322                 fails, there's nothing to do since it might be the
2323                 complete sequence, and thus, something we don't want
2324                 copied.
2325             */
2326             if (data[i] == cont_seq[cmatch])
2327             {
2328                 cmatch++;
2329                 if (cmatch == strlen(cont_seq))
2330                 {
2331                     cmatch = 0; // complete match.  just reset the counter
2332
2333                     /*
2334                         it's possible for the ICS to not include the space
2335                         at the end of the last word, making our [correct]
2336                         join operation fuse two separate words.  the server
2337                         does this when the space occurs at the width setting.
2338                     */
2339                     if (!buf_len || buf[buf_len-1] != ' ')
2340                     {
2341                         *bp++ = ' ';
2342                         buf_len++;
2343                     }
2344                 }
2345                 continue;
2346             }
2347             else if (cmatch)
2348             {
2349                 /*
2350                     match failed, so we have to copy what matched before
2351                     falling through and copying this character.  In reality,
2352                     this will only ever be just the newline character, but
2353                     it doesn't hurt to be precise.
2354                 */
2355                 strncpy(bp, cont_seq, cmatch);
2356                 bp += cmatch;
2357                 buf_len += cmatch;
2358                 cmatch = 0;
2359             }
2360         }
2361
2362         // copy this char
2363         *bp++ = data[i];
2364         buf_len++;
2365     }
2366
2367         buf[buf_len] = NULLCHAR;
2368 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2369         next_out = 0;
2370         leftover_start = 0;
2371         
2372         i = 0;
2373         while (i < buf_len) {
2374             /* Deal with part of the TELNET option negotiation
2375                protocol.  We refuse to do anything beyond the
2376                defaults, except that we allow the WILL ECHO option,
2377                which ICS uses to turn off password echoing when we are
2378                directly connected to it.  We reject this option
2379                if localLineEditing mode is on (always on in xboard)
2380                and we are talking to port 23, which might be a real
2381                telnet server that will try to keep WILL ECHO on permanently.
2382              */
2383             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2384                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2385                 unsigned char option;
2386                 oldi = i;
2387                 switch ((unsigned char) buf[++i]) {
2388                   case TN_WILL:
2389                     if (appData.debugMode)
2390                       fprintf(debugFP, "\n<WILL ");
2391                     switch (option = (unsigned char) buf[++i]) {
2392                       case TN_ECHO:
2393                         if (appData.debugMode)
2394                           fprintf(debugFP, "ECHO ");
2395                         /* Reply only if this is a change, according
2396                            to the protocol rules. */
2397                         if (remoteEchoOption) break;
2398                         if (appData.localLineEditing &&
2399                             atoi(appData.icsPort) == TN_PORT) {
2400                             TelnetRequest(TN_DONT, TN_ECHO);
2401                         } else {
2402                             EchoOff();
2403                             TelnetRequest(TN_DO, TN_ECHO);
2404                             remoteEchoOption = TRUE;
2405                         }
2406                         break;
2407                       default:
2408                         if (appData.debugMode)
2409                           fprintf(debugFP, "%d ", option);
2410                         /* Whatever this is, we don't want it. */
2411                         TelnetRequest(TN_DONT, option);
2412                         break;
2413                     }
2414                     break;
2415                   case TN_WONT:
2416                     if (appData.debugMode)
2417                       fprintf(debugFP, "\n<WONT ");
2418                     switch (option = (unsigned char) buf[++i]) {
2419                       case TN_ECHO:
2420                         if (appData.debugMode)
2421                           fprintf(debugFP, "ECHO ");
2422                         /* Reply only if this is a change, according
2423                            to the protocol rules. */
2424                         if (!remoteEchoOption) break;
2425                         EchoOn();
2426                         TelnetRequest(TN_DONT, TN_ECHO);
2427                         remoteEchoOption = FALSE;
2428                         break;
2429                       default:
2430                         if (appData.debugMode)
2431                           fprintf(debugFP, "%d ", (unsigned char) option);
2432                         /* Whatever this is, it must already be turned
2433                            off, because we never agree to turn on
2434                            anything non-default, so according to the
2435                            protocol rules, we don't reply. */
2436                         break;
2437                     }
2438                     break;
2439                   case TN_DO:
2440                     if (appData.debugMode)
2441                       fprintf(debugFP, "\n<DO ");
2442                     switch (option = (unsigned char) buf[++i]) {
2443                       default:
2444                         /* Whatever this is, we refuse to do it. */
2445                         if (appData.debugMode)
2446                           fprintf(debugFP, "%d ", option);
2447                         TelnetRequest(TN_WONT, option);
2448                         break;
2449                     }
2450                     break;
2451                   case TN_DONT:
2452                     if (appData.debugMode)
2453                       fprintf(debugFP, "\n<DONT ");
2454                     switch (option = (unsigned char) buf[++i]) {
2455                       default:
2456                         if (appData.debugMode)
2457                           fprintf(debugFP, "%d ", option);
2458                         /* Whatever this is, we are already not doing
2459                            it, because we never agree to do anything
2460                            non-default, so according to the protocol
2461                            rules, we don't reply. */
2462                         break;
2463                     }
2464                     break;
2465                   case TN_IAC:
2466                     if (appData.debugMode)
2467                       fprintf(debugFP, "\n<IAC ");
2468                     /* Doubled IAC; pass it through */
2469                     i--;
2470                     break;
2471                   default:
2472                     if (appData.debugMode)
2473                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2474                     /* Drop all other telnet commands on the floor */
2475                     break;
2476                 }
2477                 if (oldi > next_out)
2478                   SendToPlayer(&buf[next_out], oldi - next_out);
2479                 if (++i > next_out)
2480                   next_out = i;
2481                 continue;
2482             }
2483                 
2484             /* OK, this at least will *usually* work */
2485             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2486                 loggedOn = TRUE;
2487             }
2488             
2489             if (loggedOn && !intfSet) {
2490                 if (ics_type == ICS_ICC) {
2491                   sprintf(str,
2492                           "/set-quietly interface %s\n/set-quietly style 12\n",
2493                           programVersion);
2494                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2495                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2496                 } else if (ics_type == ICS_CHESSNET) {
2497                   sprintf(str, "/style 12\n");
2498                 } else {
2499                   strcpy(str, "alias $ @\n$set interface ");
2500                   strcat(str, programVersion);
2501                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2502                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2503                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2504 #ifdef WIN32
2505                   strcat(str, "$iset nohighlight 1\n");
2506 #endif
2507                   strcat(str, "$iset lock 1\n$style 12\n");
2508                 }
2509                 SendToICS(str);
2510                 NotifyFrontendLogin();
2511                 intfSet = TRUE;
2512             }
2513
2514             if (started == STARTED_COMMENT) {
2515                 /* Accumulate characters in comment */
2516                 parse[parse_pos++] = buf[i];
2517                 if (buf[i] == '\n') {
2518                     parse[parse_pos] = NULLCHAR;
2519                     if(chattingPartner>=0) {
2520                         char mess[MSG_SIZ];
2521                         sprintf(mess, "%s%s", talker, parse);
2522                         OutputChatMessage(chattingPartner, mess);
2523                         chattingPartner = -1;
2524                     } else
2525                     if(!suppressKibitz) // [HGM] kibitz
2526                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2527                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2528                         int nrDigit = 0, nrAlph = 0, j;
2529                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2530                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2531                         parse[parse_pos] = NULLCHAR;
2532                         // try to be smart: if it does not look like search info, it should go to
2533                         // ICS interaction window after all, not to engine-output window.
2534                         for(j=0; j<parse_pos; j++) { // count letters and digits
2535                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2536                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2537                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2538                         }
2539                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2540                             int depth=0; float score;
2541                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2542                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2543                                 pvInfoList[forwardMostMove-1].depth = depth;
2544                                 pvInfoList[forwardMostMove-1].score = 100*score;
2545                             }
2546                             OutputKibitz(suppressKibitz, parse);
2547                             next_out = i+1; // [HGM] suppress printing in ICS window
2548                         } else {
2549                             char tmp[MSG_SIZ];
2550                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2551                             SendToPlayer(tmp, strlen(tmp));
2552                         }
2553                     }
2554                     started = STARTED_NONE;
2555                 } else {
2556                     /* Don't match patterns against characters in comment */
2557                     i++;
2558                     continue;
2559                 }
2560             }
2561             if (started == STARTED_CHATTER) {
2562                 if (buf[i] != '\n') {
2563                     /* Don't match patterns against characters in chatter */
2564                     i++;
2565                     continue;
2566                 }
2567                 started = STARTED_NONE;
2568             }
2569
2570             /* Kludge to deal with rcmd protocol */
2571             if (firstTime && looking_at(buf, &i, "\001*")) {
2572                 DisplayFatalError(&buf[1], 0, 1);
2573                 continue;
2574             } else {
2575                 firstTime = FALSE;
2576             }
2577
2578             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2579                 ics_type = ICS_ICC;
2580                 ics_prefix = "/";
2581                 if (appData.debugMode)
2582                   fprintf(debugFP, "ics_type %d\n", ics_type);
2583                 continue;
2584             }
2585             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2586                 ics_type = ICS_FICS;
2587                 ics_prefix = "$";
2588                 if (appData.debugMode)
2589                   fprintf(debugFP, "ics_type %d\n", ics_type);
2590                 continue;
2591             }
2592             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2593                 ics_type = ICS_CHESSNET;
2594                 ics_prefix = "/";
2595                 if (appData.debugMode)
2596                   fprintf(debugFP, "ics_type %d\n", ics_type);
2597                 continue;
2598             }
2599
2600             if (!loggedOn &&
2601                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2602                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2603                  looking_at(buf, &i, "will be \"*\""))) {
2604               strcpy(ics_handle, star_match[0]);
2605               continue;
2606             }
2607
2608             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2609               char buf[MSG_SIZ];
2610               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2611               DisplayIcsInteractionTitle(buf);
2612               have_set_title = TRUE;
2613             }
2614
2615             /* skip finger notes */
2616             if (started == STARTED_NONE &&
2617                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2618                  (buf[i] == '1' && buf[i+1] == '0')) &&
2619                 buf[i+2] == ':' && buf[i+3] == ' ') {
2620               started = STARTED_CHATTER;
2621               i += 3;
2622               continue;
2623             }
2624
2625             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2626             if(appData.seekGraph) {
2627                 if(soughtPending && MatchSoughtLine(buf+i)) {
2628                     i = strstr(buf+i, "rated") - buf;
2629                     next_out = leftover_start = i;
2630                     started = STARTED_CHATTER;
2631                     suppressKibitz = TRUE;
2632                     continue;
2633                 }
2634                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2635                         && looking_at(buf, &i, "* ads displayed")) {
2636                     soughtPending = FALSE;
2637                     seekGraphUp = TRUE;
2638                     DrawSeekGraph();
2639                     continue;
2640                 }
2641                 if(appData.autoRefresh) {
2642                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2643                         int s = (ics_type == ICS_ICC); // ICC format differs
2644                         if(seekGraphUp)
2645                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2646                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2647                         looking_at(buf, &i, "*% "); // eat prompt
2648                         next_out = i; // suppress
2649                         continue;
2650                     }
2651                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2652                         char *p = star_match[0];
2653                         while(*p) {
2654                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2655                             while(*p && *p++ != ' '); // next
2656                         }
2657                         looking_at(buf, &i, "*% "); // eat prompt
2658                         next_out = i;
2659                         continue;
2660                     }
2661                 }
2662             }
2663
2664             /* skip formula vars */
2665             if (started == STARTED_NONE &&
2666                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2667               started = STARTED_CHATTER;
2668               i += 3;
2669               continue;
2670             }
2671
2672             oldi = i;
2673             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2674             if (appData.autoKibitz && started == STARTED_NONE && 
2675                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2676                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2677                 if(looking_at(buf, &i, "* kibitzes: ") &&
2678                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2679                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2680                         suppressKibitz = TRUE;
2681                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2682                                 && (gameMode == IcsPlayingWhite)) ||
2683                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2684                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2685                             started = STARTED_CHATTER; // own kibitz we simply discard
2686                         else {
2687                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2688                             parse_pos = 0; parse[0] = NULLCHAR;
2689                             savingComment = TRUE;
2690                             suppressKibitz = gameMode != IcsObserving ? 2 :
2691                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2692                         } 
2693                         continue;
2694                 } else
2695                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2696                     // suppress the acknowledgements of our own autoKibitz
2697                     char *p;
2698                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2699                     SendToPlayer(star_match[0], strlen(star_match[0]));
2700                     looking_at(buf, &i, "*% "); // eat prompt
2701                     next_out = i;
2702                 }
2703             } // [HGM] kibitz: end of patch
2704
2705 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2706
2707             // [HGM] chat: intercept tells by users for which we have an open chat window
2708             channel = -1;
2709             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2710                                            looking_at(buf, &i, "* whispers:") ||
2711                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2712                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2713                 int p;
2714                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2715                 chattingPartner = -1;
2716
2717                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2718                 for(p=0; p<MAX_CHAT; p++) {
2719                     if(channel == atoi(chatPartner[p])) {
2720                     talker[0] = '['; strcat(talker, "] ");
2721                     chattingPartner = p; break;
2722                     }
2723                 } else
2724                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2725                 for(p=0; p<MAX_CHAT; p++) {
2726                     if(!strcmp("WHISPER", chatPartner[p])) {
2727                         talker[0] = '['; strcat(talker, "] ");
2728                         chattingPartner = p; break;
2729                     }
2730                 }
2731                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2732                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2733                     talker[0] = 0;
2734                     chattingPartner = p; break;
2735                 }
2736                 if(chattingPartner<0) i = oldi; else {
2737                     started = STARTED_COMMENT;
2738                     parse_pos = 0; parse[0] = NULLCHAR;
2739                     savingComment = 3 + chattingPartner; // counts as TRUE
2740                     suppressKibitz = TRUE;
2741                 }
2742             } // [HGM] chat: end of patch
2743
2744             if (appData.zippyTalk || appData.zippyPlay) {
2745                 /* [DM] Backup address for color zippy lines */
2746                 backup = i;
2747 #if ZIPPY
2748        #ifdef WIN32
2749                if (loggedOn == TRUE)
2750                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2751                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2752        #else
2753                 if (ZippyControl(buf, &i) ||
2754                     ZippyConverse(buf, &i) ||
2755                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2756                       loggedOn = TRUE;
2757                       if (!appData.colorize) continue;
2758                 }
2759        #endif
2760 #endif
2761             } // [DM] 'else { ' deleted
2762                 if (
2763                     /* Regular tells and says */
2764                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2765                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2766                     looking_at(buf, &i, "* says: ") ||
2767                     /* Don't color "message" or "messages" output */
2768                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2769                     looking_at(buf, &i, "*. * at *:*: ") ||
2770                     looking_at(buf, &i, "--* (*:*): ") ||
2771                     /* Message notifications (same color as tells) */
2772                     looking_at(buf, &i, "* has left a message ") ||
2773                     looking_at(buf, &i, "* just sent you a message:\n") ||
2774                     /* Whispers and kibitzes */
2775                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2776                     looking_at(buf, &i, "* kibitzes: ") ||
2777                     /* Channel tells */
2778                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2779
2780                   if (tkind == 1 && strchr(star_match[0], ':')) {
2781                       /* Avoid "tells you:" spoofs in channels */
2782                      tkind = 3;
2783                   }
2784                   if (star_match[0][0] == NULLCHAR ||
2785                       strchr(star_match[0], ' ') ||
2786                       (tkind == 3 && strchr(star_match[1], ' '))) {
2787                     /* Reject bogus matches */
2788                     i = oldi;
2789                   } else {
2790                     if (appData.colorize) {
2791                       if (oldi > next_out) {
2792                         SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = oldi;
2794                       }
2795                       switch (tkind) {
2796                       case 1:
2797                         Colorize(ColorTell, FALSE);
2798                         curColor = ColorTell;
2799                         break;
2800                       case 2:
2801                         Colorize(ColorKibitz, FALSE);
2802                         curColor = ColorKibitz;
2803                         break;
2804                       case 3:
2805                         p = strrchr(star_match[1], '(');
2806                         if (p == NULL) {
2807                           p = star_match[1];
2808                         } else {
2809                           p++;
2810                         }
2811                         if (atoi(p) == 1) {
2812                           Colorize(ColorChannel1, FALSE);
2813                           curColor = ColorChannel1;
2814                         } else {
2815                           Colorize(ColorChannel, FALSE);
2816                           curColor = ColorChannel;
2817                         }
2818                         break;
2819                       case 5:
2820                         curColor = ColorNormal;
2821                         break;
2822                       }
2823                     }
2824                     if (started == STARTED_NONE && appData.autoComment &&
2825                         (gameMode == IcsObserving ||
2826                          gameMode == IcsPlayingWhite ||
2827                          gameMode == IcsPlayingBlack)) {
2828                       parse_pos = i - oldi;
2829                       memcpy(parse, &buf[oldi], parse_pos);
2830                       parse[parse_pos] = NULLCHAR;
2831                       started = STARTED_COMMENT;
2832                       savingComment = TRUE;
2833                     } else {
2834                       started = STARTED_CHATTER;
2835                       savingComment = FALSE;
2836                     }
2837                     loggedOn = TRUE;
2838                     continue;
2839                   }
2840                 }
2841
2842                 if (looking_at(buf, &i, "* s-shouts: ") ||
2843                     looking_at(buf, &i, "* c-shouts: ")) {
2844                     if (appData.colorize) {
2845                         if (oldi > next_out) {
2846                             SendToPlayer(&buf[next_out], oldi - next_out);
2847                             next_out = oldi;
2848                         }
2849                         Colorize(ColorSShout, FALSE);
2850                         curColor = ColorSShout;
2851                     }
2852                     loggedOn = TRUE;
2853                     started = STARTED_CHATTER;
2854                     continue;
2855                 }
2856
2857                 if (looking_at(buf, &i, "--->")) {
2858                     loggedOn = TRUE;
2859                     continue;
2860                 }
2861
2862                 if (looking_at(buf, &i, "* shouts: ") ||
2863                     looking_at(buf, &i, "--> ")) {
2864                     if (appData.colorize) {
2865                         if (oldi > next_out) {
2866                             SendToPlayer(&buf[next_out], oldi - next_out);
2867                             next_out = oldi;
2868                         }
2869                         Colorize(ColorShout, FALSE);
2870                         curColor = ColorShout;
2871                     }
2872                     loggedOn = TRUE;
2873                     started = STARTED_CHATTER;
2874                     continue;
2875                 }
2876
2877                 if (looking_at( buf, &i, "Challenge:")) {
2878                     if (appData.colorize) {
2879                         if (oldi > next_out) {
2880                             SendToPlayer(&buf[next_out], oldi - next_out);
2881                             next_out = oldi;
2882                         }
2883                         Colorize(ColorChallenge, FALSE);
2884                         curColor = ColorChallenge;
2885                     }
2886                     loggedOn = TRUE;
2887                     continue;
2888                 }
2889
2890                 if (looking_at(buf, &i, "* offers you") ||
2891                     looking_at(buf, &i, "* offers to be") ||
2892                     looking_at(buf, &i, "* would like to") ||
2893                     looking_at(buf, &i, "* requests to") ||
2894                     looking_at(buf, &i, "Your opponent offers") ||
2895                     looking_at(buf, &i, "Your opponent requests")) {
2896
2897                     if (appData.colorize) {
2898                         if (oldi > next_out) {
2899                             SendToPlayer(&buf[next_out], oldi - next_out);
2900                             next_out = oldi;
2901                         }
2902                         Colorize(ColorRequest, FALSE);
2903                         curColor = ColorRequest;
2904                     }
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "* (*) seeking")) {
2909                     if (appData.colorize) {
2910                         if (oldi > next_out) {
2911                             SendToPlayer(&buf[next_out], oldi - next_out);
2912                             next_out = oldi;
2913                         }
2914                         Colorize(ColorSeek, FALSE);
2915                         curColor = ColorSeek;
2916                     }
2917                     continue;
2918             }
2919
2920             if (looking_at(buf, &i, "\\   ")) {
2921                 if (prevColor != ColorNormal) {
2922                     if (oldi > next_out) {
2923                         SendToPlayer(&buf[next_out], oldi - next_out);
2924                         next_out = oldi;
2925                     }
2926                     Colorize(prevColor, TRUE);
2927                     curColor = prevColor;
2928                 }
2929                 if (savingComment) {
2930                     parse_pos = i - oldi;
2931                     memcpy(parse, &buf[oldi], parse_pos);
2932                     parse[parse_pos] = NULLCHAR;
2933                     started = STARTED_COMMENT;
2934                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2935                         chattingPartner = savingComment - 3; // kludge to remember the box
2936                 } else {
2937                     started = STARTED_CHATTER;
2938                 }
2939                 continue;
2940             }
2941
2942             if (looking_at(buf, &i, "Black Strength :") ||
2943                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2944                 looking_at(buf, &i, "<10>") ||
2945                 looking_at(buf, &i, "#@#")) {
2946                 /* Wrong board style */
2947                 loggedOn = TRUE;
2948                 SendToICS(ics_prefix);
2949                 SendToICS("set style 12\n");
2950                 SendToICS(ics_prefix);
2951                 SendToICS("refresh\n");
2952                 continue;
2953             }
2954             
2955             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2956                 ICSInitScript();
2957                 have_sent_ICS_logon = 1;
2958                 continue;
2959             }
2960               
2961             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2962                 (looking_at(buf, &i, "\n<12> ") ||
2963                  looking_at(buf, &i, "<12> "))) {
2964                 loggedOn = TRUE;
2965                 if (oldi > next_out) {
2966                     SendToPlayer(&buf[next_out], oldi - next_out);
2967                 }
2968                 next_out = i;
2969                 started = STARTED_BOARD;
2970                 parse_pos = 0;
2971                 continue;
2972             }
2973
2974             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2975                 looking_at(buf, &i, "<b1> ")) {
2976                 if (oldi > next_out) {
2977                     SendToPlayer(&buf[next_out], oldi - next_out);
2978                 }
2979                 next_out = i;
2980                 started = STARTED_HOLDINGS;
2981                 parse_pos = 0;
2982                 continue;
2983             }
2984
2985             if (looking_at(buf, &i, "* *vs. * *--- *")) {
2986                 loggedOn = TRUE;
2987                 /* Header for a move list -- first line */
2988
2989                 switch (ics_getting_history) {
2990                   case H_FALSE:
2991                     switch (gameMode) {
2992                       case IcsIdle:
2993                       case BeginningOfGame:
2994                         /* User typed "moves" or "oldmoves" while we
2995                            were idle.  Pretend we asked for these
2996                            moves and soak them up so user can step
2997                            through them and/or save them.
2998                            */
2999                         Reset(FALSE, TRUE);
3000                         gameMode = IcsObserving;
3001                         ModeHighlight();
3002                         ics_gamenum = -1;
3003                         ics_getting_history = H_GOT_UNREQ_HEADER;
3004                         break;
3005                       case EditGame: /*?*/
3006                       case EditPosition: /*?*/
3007                         /* Should above feature work in these modes too? */
3008                         /* For now it doesn't */
3009                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3010                         break;
3011                       default:
3012                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3013                         break;
3014                     }
3015                     break;
3016                   case H_REQUESTED:
3017                     /* Is this the right one? */
3018                     if (gameInfo.white && gameInfo.black &&
3019                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3020                         strcmp(gameInfo.black, star_match[2]) == 0) {
3021                         /* All is well */
3022                         ics_getting_history = H_GOT_REQ_HEADER;
3023                     }
3024                     break;
3025                   case H_GOT_REQ_HEADER:
3026                   case H_GOT_UNREQ_HEADER:
3027                   case H_GOT_UNWANTED_HEADER:
3028                   case H_GETTING_MOVES:
3029                     /* Should not happen */
3030                     DisplayError(_("Error gathering move list: two headers"), 0);
3031                     ics_getting_history = H_FALSE;
3032                     break;
3033                 }
3034
3035                 /* Save player ratings into gameInfo if needed */
3036                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3037                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3038                     (gameInfo.whiteRating == -1 ||
3039                      gameInfo.blackRating == -1)) {
3040
3041                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3042                     gameInfo.blackRating = string_to_rating(star_match[3]);
3043                     if (appData.debugMode)
3044                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3045                               gameInfo.whiteRating, gameInfo.blackRating);
3046                 }
3047                 continue;
3048             }
3049
3050             if (looking_at(buf, &i,
3051               "* * match, initial time: * minute*, increment: * second")) {
3052                 /* Header for a move list -- second line */
3053                 /* Initial board will follow if this is a wild game */
3054                 if (gameInfo.event != NULL) free(gameInfo.event);
3055                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3056                 gameInfo.event = StrSave(str);
3057                 /* [HGM] we switched variant. Translate boards if needed. */
3058                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3059                 continue;
3060             }
3061
3062             if (looking_at(buf, &i, "Move  ")) {
3063                 /* Beginning of a move list */
3064                 switch (ics_getting_history) {
3065                   case H_FALSE:
3066                     /* Normally should not happen */
3067                     /* Maybe user hit reset while we were parsing */
3068                     break;
3069                   case H_REQUESTED:
3070                     /* Happens if we are ignoring a move list that is not
3071                      * the one we just requested.  Common if the user
3072                      * tries to observe two games without turning off
3073                      * getMoveList */
3074                     break;
3075                   case H_GETTING_MOVES:
3076                     /* Should not happen */
3077                     DisplayError(_("Error gathering move list: nested"), 0);
3078                     ics_getting_history = H_FALSE;
3079                     break;
3080                   case H_GOT_REQ_HEADER:
3081                     ics_getting_history = H_GETTING_MOVES;
3082                     started = STARTED_MOVES;
3083                     parse_pos = 0;
3084                     if (oldi > next_out) {
3085                         SendToPlayer(&buf[next_out], oldi - next_out);
3086                     }
3087                     break;
3088                   case H_GOT_UNREQ_HEADER:
3089                     ics_getting_history = H_GETTING_MOVES;
3090                     started = STARTED_MOVES_NOHIDE;
3091                     parse_pos = 0;
3092                     break;
3093                   case H_GOT_UNWANTED_HEADER:
3094                     ics_getting_history = H_FALSE;
3095                     break;
3096                 }
3097                 continue;
3098             }                           
3099             
3100             if (looking_at(buf, &i, "% ") ||
3101                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3102                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3103                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3104                     soughtPending = FALSE;
3105                     seekGraphUp = TRUE;
3106                     DrawSeekGraph();
3107                 }
3108                 if(suppressKibitz) next_out = i;
3109                 savingComment = FALSE;
3110                 suppressKibitz = 0;
3111                 switch (started) {
3112                   case STARTED_MOVES:
3113                   case STARTED_MOVES_NOHIDE:
3114                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3115                     parse[parse_pos + i - oldi] = NULLCHAR;
3116                     ParseGameHistory(parse);
3117 #if ZIPPY
3118                     if (appData.zippyPlay && first.initDone) {
3119                         FeedMovesToProgram(&first, forwardMostMove);
3120                         if (gameMode == IcsPlayingWhite) {
3121                             if (WhiteOnMove(forwardMostMove)) {
3122                                 if (first.sendTime) {
3123                                   if (first.useColors) {
3124                                     SendToProgram("black\n", &first); 
3125                                   }
3126                                   SendTimeRemaining(&first, TRUE);
3127                                 }
3128                                 if (first.useColors) {
3129                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3130                                 }
3131                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3132                                 first.maybeThinking = TRUE;
3133                             } else {
3134                                 if (first.usePlayother) {
3135                                   if (first.sendTime) {
3136                                     SendTimeRemaining(&first, TRUE);
3137                                   }
3138                                   SendToProgram("playother\n", &first);
3139                                   firstMove = FALSE;
3140                                 } else {
3141                                   firstMove = TRUE;
3142                                 }
3143                             }
3144                         } else if (gameMode == IcsPlayingBlack) {
3145                             if (!WhiteOnMove(forwardMostMove)) {
3146                                 if (first.sendTime) {
3147                                   if (first.useColors) {
3148                                     SendToProgram("white\n", &first);
3149                                   }
3150                                   SendTimeRemaining(&first, FALSE);
3151                                 }
3152                                 if (first.useColors) {
3153                                   SendToProgram("black\n", &first);
3154                                 }
3155                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3156                                 first.maybeThinking = TRUE;
3157                             } else {
3158                                 if (first.usePlayother) {
3159                                   if (first.sendTime) {
3160                                     SendTimeRemaining(&first, FALSE);
3161                                   }
3162                                   SendToProgram("playother\n", &first);
3163                                   firstMove = FALSE;
3164                                 } else {
3165                                   firstMove = TRUE;
3166                                 }
3167                             }
3168                         }                       
3169                     }
3170 #endif
3171                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3172                         /* Moves came from oldmoves or moves command
3173                            while we weren't doing anything else.
3174                            */
3175                         currentMove = forwardMostMove;
3176                         ClearHighlights();/*!!could figure this out*/
3177                         flipView = appData.flipView;
3178                         DrawPosition(TRUE, boards[currentMove]);
3179                         DisplayBothClocks();
3180                         sprintf(str, "%s vs. %s",
3181                                 gameInfo.white, gameInfo.black);
3182                         DisplayTitle(str);
3183                         gameMode = IcsIdle;
3184                     } else {
3185                         /* Moves were history of an active game */
3186                         if (gameInfo.resultDetails != NULL) {
3187                             free(gameInfo.resultDetails);
3188                             gameInfo.resultDetails = NULL;
3189                         }
3190                     }
3191                     HistorySet(parseList, backwardMostMove,
3192                                forwardMostMove, currentMove-1);
3193                     DisplayMove(currentMove - 1);
3194                     if (started == STARTED_MOVES) next_out = i;
3195                     started = STARTED_NONE;
3196                     ics_getting_history = H_FALSE;
3197                     break;
3198
3199                   case STARTED_OBSERVE:
3200                     started = STARTED_NONE;
3201                     SendToICS(ics_prefix);
3202                     SendToICS("refresh\n");
3203                     break;
3204
3205                   default:
3206                     break;
3207                 }
3208                 if(bookHit) { // [HGM] book: simulate book reply
3209                     static char bookMove[MSG_SIZ]; // a bit generous?
3210
3211                     programStats.nodes = programStats.depth = programStats.time = 
3212                     programStats.score = programStats.got_only_move = 0;
3213                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3214
3215                     strcpy(bookMove, "move ");
3216                     strcat(bookMove, bookHit);
3217                     HandleMachineMove(bookMove, &first);
3218                 }
3219                 continue;
3220             }
3221             
3222             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3223                  started == STARTED_HOLDINGS ||
3224                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3225                 /* Accumulate characters in move list or board */
3226                 parse[parse_pos++] = buf[i];
3227             }
3228             
3229             /* Start of game messages.  Mostly we detect start of game
3230                when the first board image arrives.  On some versions
3231                of the ICS, though, we need to do a "refresh" after starting
3232                to observe in order to get the current board right away. */
3233             if (looking_at(buf, &i, "Adding game * to observation list")) {
3234                 started = STARTED_OBSERVE;
3235                 continue;
3236             }
3237
3238             /* Handle auto-observe */
3239             if (appData.autoObserve &&
3240                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3241                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3242                 char *player;
3243                 /* Choose the player that was highlighted, if any. */
3244                 if (star_match[0][0] == '\033' ||
3245                     star_match[1][0] != '\033') {
3246                     player = star_match[0];
3247                 } else {
3248                     player = star_match[2];
3249                 }
3250                 sprintf(str, "%sobserve %s\n",
3251                         ics_prefix, StripHighlightAndTitle(player));
3252                 SendToICS(str);
3253
3254                 /* Save ratings from notify string */
3255                 strcpy(player1Name, star_match[0]);
3256                 player1Rating = string_to_rating(star_match[1]);
3257                 strcpy(player2Name, star_match[2]);
3258                 player2Rating = string_to_rating(star_match[3]);
3259
3260                 if (appData.debugMode)
3261                   fprintf(debugFP, 
3262                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3263                           player1Name, player1Rating,
3264                           player2Name, player2Rating);
3265
3266                 continue;
3267             }
3268
3269             /* Deal with automatic examine mode after a game,
3270                and with IcsObserving -> IcsExamining transition */
3271             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3272                 looking_at(buf, &i, "has made you an examiner of game *")) {
3273
3274                 int gamenum = atoi(star_match[0]);
3275                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3276                     gamenum == ics_gamenum) {
3277                     /* We were already playing or observing this game;
3278                        no need to refetch history */
3279                     gameMode = IcsExamining;
3280                     if (pausing) {
3281                         pauseExamForwardMostMove = forwardMostMove;
3282                     } else if (currentMove < forwardMostMove) {
3283                         ForwardInner(forwardMostMove);
3284                     }
3285                 } else {
3286                     /* I don't think this case really can happen */
3287                     SendToICS(ics_prefix);
3288                     SendToICS("refresh\n");
3289                 }
3290                 continue;
3291             }    
3292             
3293             /* Error messages */
3294 //          if (ics_user_moved) {
3295             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3296                 if (looking_at(buf, &i, "Illegal move") ||
3297                     looking_at(buf, &i, "Not a legal move") ||
3298                     looking_at(buf, &i, "Your king is in check") ||
3299                     looking_at(buf, &i, "It isn't your turn") ||
3300                     looking_at(buf, &i, "It is not your move")) {
3301                     /* Illegal move */
3302                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3303                         currentMove = --forwardMostMove;
3304                         DisplayMove(currentMove - 1); /* before DMError */
3305                         DrawPosition(FALSE, boards[currentMove]);
3306                         SwitchClocks();
3307                         DisplayBothClocks();
3308                     }
3309                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3310                     ics_user_moved = 0;
3311                     continue;
3312                 }
3313             }
3314
3315             if (looking_at(buf, &i, "still have time") ||
3316                 looking_at(buf, &i, "not out of time") ||
3317                 looking_at(buf, &i, "either player is out of time") ||
3318                 looking_at(buf, &i, "has timeseal; checking")) {
3319                 /* We must have called his flag a little too soon */
3320                 whiteFlag = blackFlag = FALSE;
3321                 continue;
3322             }
3323
3324             if (looking_at(buf, &i, "added * seconds to") ||
3325                 looking_at(buf, &i, "seconds were added to")) {
3326                 /* Update the clocks */
3327                 SendToICS(ics_prefix);
3328                 SendToICS("refresh\n");
3329                 continue;
3330             }
3331
3332             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3333                 ics_clock_paused = TRUE;
3334                 StopClocks();
3335                 continue;
3336             }
3337
3338             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3339                 ics_clock_paused = FALSE;
3340                 StartClocks();
3341                 continue;
3342             }
3343
3344             /* Grab player ratings from the Creating: message.
3345                Note we have to check for the special case when
3346                the ICS inserts things like [white] or [black]. */
3347             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3348                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3349                 /* star_matches:
3350                    0    player 1 name (not necessarily white)
3351                    1    player 1 rating
3352                    2    empty, white, or black (IGNORED)
3353                    3    player 2 name (not necessarily black)
3354                    4    player 2 rating
3355                    
3356                    The names/ratings are sorted out when the game
3357                    actually starts (below).
3358                 */
3359                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3360                 player1Rating = string_to_rating(star_match[1]);
3361                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3362                 player2Rating = string_to_rating(star_match[4]);
3363
3364                 if (appData.debugMode)
3365                   fprintf(debugFP, 
3366                           "Ratings from 'Creating:' %s %d, %s %d\n",
3367                           player1Name, player1Rating,
3368                           player2Name, player2Rating);
3369
3370                 continue;
3371             }
3372             
3373             /* Improved generic start/end-of-game messages */
3374             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3375                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3376                 /* If tkind == 0: */
3377                 /* star_match[0] is the game number */
3378                 /*           [1] is the white player's name */
3379                 /*           [2] is the black player's name */
3380                 /* For end-of-game: */
3381                 /*           [3] is the reason for the game end */
3382                 /*           [4] is a PGN end game-token, preceded by " " */
3383                 /* For start-of-game: */
3384                 /*           [3] begins with "Creating" or "Continuing" */
3385                 /*           [4] is " *" or empty (don't care). */
3386                 int gamenum = atoi(star_match[0]);
3387                 char *whitename, *blackname, *why, *endtoken;
3388                 ChessMove endtype = (ChessMove) 0;
3389
3390                 if (tkind == 0) {
3391                   whitename = star_match[1];
3392                   blackname = star_match[2];
3393                   why = star_match[3];
3394                   endtoken = star_match[4];
3395                 } else {
3396                   whitename = star_match[1];
3397                   blackname = star_match[3];
3398                   why = star_match[5];
3399                   endtoken = star_match[6];
3400                 }
3401
3402                 /* Game start messages */
3403                 if (strncmp(why, "Creating ", 9) == 0 ||
3404                     strncmp(why, "Continuing ", 11) == 0) {
3405                     gs_gamenum = gamenum;
3406                     strcpy(gs_kind, strchr(why, ' ') + 1);
3407                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3408 #if ZIPPY
3409                     if (appData.zippyPlay) {
3410                         ZippyGameStart(whitename, blackname);
3411                     }
3412 #endif /*ZIPPY*/
3413                     continue;
3414                 }
3415
3416                 /* Game end messages */
3417                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3418                     ics_gamenum != gamenum) {
3419                     continue;
3420                 }
3421                 while (endtoken[0] == ' ') endtoken++;
3422                 switch (endtoken[0]) {
3423                   case '*':
3424                   default:
3425                     endtype = GameUnfinished;
3426                     break;
3427                   case '0':
3428                     endtype = BlackWins;
3429                     break;
3430                   case '1':
3431                     if (endtoken[1] == '/')
3432                       endtype = GameIsDrawn;
3433                     else
3434                       endtype = WhiteWins;
3435                     break;
3436                 }
3437                 GameEnds(endtype, why, GE_ICS);
3438 #if ZIPPY
3439                 if (appData.zippyPlay && first.initDone) {
3440                     ZippyGameEnd(endtype, why);
3441                     if (first.pr == NULL) {
3442                       /* Start the next process early so that we'll
3443                          be ready for the next challenge */
3444                       StartChessProgram(&first);
3445                     }
3446                     /* Send "new" early, in case this command takes
3447                        a long time to finish, so that we'll be ready
3448                        for the next challenge. */
3449                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3450                     Reset(TRUE, TRUE);
3451                 }
3452 #endif /*ZIPPY*/
3453                 continue;
3454             }
3455
3456             if (looking_at(buf, &i, "Removing game * from observation") ||
3457                 looking_at(buf, &i, "no longer observing game *") ||
3458                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3459                 if (gameMode == IcsObserving &&
3460                     atoi(star_match[0]) == ics_gamenum)
3461                   {
3462                       /* icsEngineAnalyze */
3463                       if (appData.icsEngineAnalyze) {
3464                             ExitAnalyzeMode();
3465                             ModeHighlight();
3466                       }
3467                       StopClocks();
3468                       gameMode = IcsIdle;
3469                       ics_gamenum = -1;
3470                       ics_user_moved = FALSE;
3471                   }
3472                 continue;
3473             }
3474
3475             if (looking_at(buf, &i, "no longer examining game *")) {
3476                 if (gameMode == IcsExamining &&
3477                     atoi(star_match[0]) == ics_gamenum)
3478                   {
3479                       gameMode = IcsIdle;
3480                       ics_gamenum = -1;
3481                       ics_user_moved = FALSE;
3482                   }
3483                 continue;
3484             }
3485
3486             /* Advance leftover_start past any newlines we find,
3487                so only partial lines can get reparsed */
3488             if (looking_at(buf, &i, "\n")) {
3489                 prevColor = curColor;
3490                 if (curColor != ColorNormal) {
3491                     if (oldi > next_out) {
3492                         SendToPlayer(&buf[next_out], oldi - next_out);
3493                         next_out = oldi;
3494                     }
3495                     Colorize(ColorNormal, FALSE);
3496                     curColor = ColorNormal;
3497                 }
3498                 if (started == STARTED_BOARD) {
3499                     started = STARTED_NONE;
3500                     parse[parse_pos] = NULLCHAR;
3501                     ParseBoard12(parse);
3502                     ics_user_moved = 0;
3503
3504                     /* Send premove here */
3505                     if (appData.premove) {
3506                       char str[MSG_SIZ];
3507                       if (currentMove == 0 &&
3508                           gameMode == IcsPlayingWhite &&
3509                           appData.premoveWhite) {
3510                         sprintf(str, "%s\n", appData.premoveWhiteText);
3511                         if (appData.debugMode)
3512                           fprintf(debugFP, "Sending premove:\n");
3513                         SendToICS(str);
3514                       } else if (currentMove == 1 &&
3515                                  gameMode == IcsPlayingBlack &&
3516                                  appData.premoveBlack) {
3517                         sprintf(str, "%s\n", appData.premoveBlackText);
3518                         if (appData.debugMode)
3519                           fprintf(debugFP, "Sending premove:\n");
3520                         SendToICS(str);
3521                       } else if (gotPremove) {
3522                         gotPremove = 0;
3523                         ClearPremoveHighlights();
3524                         if (appData.debugMode)
3525                           fprintf(debugFP, "Sending premove:\n");
3526                           UserMoveEvent(premoveFromX, premoveFromY, 
3527                                         premoveToX, premoveToY, 
3528                                         premovePromoChar);
3529                       }
3530                     }
3531
3532                     /* Usually suppress following prompt */
3533                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3534                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3535                         if (looking_at(buf, &i, "*% ")) {
3536                             savingComment = FALSE;
3537                             suppressKibitz = 0;
3538                         }
3539                     }
3540                     next_out = i;
3541                 } else if (started == STARTED_HOLDINGS) {
3542                     int gamenum;
3543                     char new_piece[MSG_SIZ];
3544                     started = STARTED_NONE;
3545                     parse[parse_pos] = NULLCHAR;
3546                     if (appData.debugMode)
3547                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3548                                                         parse, currentMove);
3549                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3550                         gamenum == ics_gamenum) {
3551                         if (gameInfo.variant == VariantNormal) {
3552                           /* [HGM] We seem to switch variant during a game!
3553                            * Presumably no holdings were displayed, so we have
3554                            * to move the position two files to the right to
3555                            * create room for them!
3556                            */
3557                           VariantClass newVariant;
3558                           switch(gameInfo.boardWidth) { // base guess on board width
3559                                 case 9:  newVariant = VariantShogi; break;
3560                                 case 10: newVariant = VariantGreat; break;
3561                                 default: newVariant = VariantCrazyhouse; break;
3562                           }
3563                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3564                           /* Get a move list just to see the header, which
3565                              will tell us whether this is really bug or zh */
3566                           if (ics_getting_history == H_FALSE) {
3567                             ics_getting_history = H_REQUESTED;
3568                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3569                             SendToICS(str);
3570                           }
3571                         }
3572                         new_piece[0] = NULLCHAR;
3573                         sscanf(parse, "game %d white [%s black [%s <- %s",
3574                                &gamenum, white_holding, black_holding,
3575                                new_piece);
3576                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3577                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3578                         /* [HGM] copy holdings to board holdings area */
3579                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3580                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3581                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3582 #if ZIPPY
3583                         if (appData.zippyPlay && first.initDone) {
3584                             ZippyHoldings(white_holding, black_holding,
3585                                           new_piece);
3586                         }
3587 #endif /*ZIPPY*/
3588                         if (tinyLayout || smallLayout) {
3589                             char wh[16], bh[16];
3590                             PackHolding(wh, white_holding);
3591                             PackHolding(bh, black_holding);
3592                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3593                                     gameInfo.white, gameInfo.black);
3594                         } else {
3595                             sprintf(str, "%s [%s] vs. %s [%s]",
3596                                     gameInfo.white, white_holding,
3597                                     gameInfo.black, black_holding);
3598                         }
3599
3600                         DrawPosition(FALSE, boards[currentMove]);
3601                         DisplayTitle(str);
3602                     }
3603                     /* Suppress following prompt */
3604                     if (looking_at(buf, &i, "*% ")) {
3605                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3606                         savingComment = FALSE;
3607                         suppressKibitz = 0;
3608                     }
3609                     next_out = i;
3610                 }
3611                 continue;
3612             }
3613
3614             i++;                /* skip unparsed character and loop back */
3615         }
3616         
3617         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3618 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3619 //          SendToPlayer(&buf[next_out], i - next_out);
3620             started != STARTED_HOLDINGS && leftover_start > next_out) {
3621             SendToPlayer(&buf[next_out], leftover_start - next_out);
3622             next_out = i;
3623         }
3624         
3625         leftover_len = buf_len - leftover_start;
3626         /* if buffer ends with something we couldn't parse,
3627            reparse it after appending the next read */
3628         
3629     } else if (count == 0) {
3630         RemoveInputSource(isr);
3631         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3632     } else {
3633         DisplayFatalError(_("Error reading from ICS"), error, 1);
3634     }
3635 }
3636
3637
3638 /* Board style 12 looks like this:
3639    
3640    <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
3641    
3642  * The "<12> " is stripped before it gets to this routine.  The two
3643  * trailing 0's (flip state and clock ticking) are later addition, and
3644  * some chess servers may not have them, or may have only the first.
3645  * Additional trailing fields may be added in the future.  
3646  */
3647
3648 #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"
3649
3650 #define RELATION_OBSERVING_PLAYED    0
3651 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3652 #define RELATION_PLAYING_MYMOVE      1
3653 #define RELATION_PLAYING_NOTMYMOVE  -1
3654 #define RELATION_EXAMINING           2
3655 #define RELATION_ISOLATED_BOARD     -3
3656 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3657
3658 void
3659 ParseBoard12(string)
3660      char *string;
3661
3662     GameMode newGameMode;
3663     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3664     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3665     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3666     char to_play, board_chars[200];
3667     char move_str[500], str[500], elapsed_time[500];
3668     char black[32], white[32];
3669     Board board;
3670     int prevMove = currentMove;
3671     int ticking = 2;
3672     ChessMove moveType;
3673     int fromX, fromY, toX, toY;
3674     char promoChar;
3675     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3676     char *bookHit = NULL; // [HGM] book
3677     Boolean weird = FALSE, reqFlag = FALSE;
3678
3679     fromX = fromY = toX = toY = -1;
3680     
3681     newGame = FALSE;
3682
3683     if (appData.debugMode)
3684       fprintf(debugFP, _("Parsing board: %s\n"), string);
3685
3686     move_str[0] = NULLCHAR;
3687     elapsed_time[0] = NULLCHAR;
3688     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3689         int  i = 0, j;
3690         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3691             if(string[i] == ' ') { ranks++; files = 0; }
3692             else files++;
3693             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3694             i++;
3695         }
3696         for(j = 0; j <i; j++) board_chars[j] = string[j];
3697         board_chars[i] = '\0';
3698         string += i + 1;
3699     }
3700     n = sscanf(string, PATTERN, &to_play, &double_push,
3701                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3702                &gamenum, white, black, &relation, &basetime, &increment,
3703                &white_stren, &black_stren, &white_time, &black_time,
3704                &moveNum, str, elapsed_time, move_str, &ics_flip,
3705                &ticking);
3706
3707     if (n < 21) {
3708         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3709         DisplayError(str, 0);
3710         return;
3711     }
3712
3713     /* Convert the move number to internal form */
3714     moveNum = (moveNum - 1) * 2;
3715     if (to_play == 'B') moveNum++;
3716     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3717       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3718                         0, 1);
3719       return;
3720     }
3721     
3722     switch (relation) {
3723       case RELATION_OBSERVING_PLAYED:
3724       case RELATION_OBSERVING_STATIC:
3725         if (gamenum == -1) {
3726             /* Old ICC buglet */
3727             relation = RELATION_OBSERVING_STATIC;
3728         }
3729         newGameMode = IcsObserving;
3730         break;
3731       case RELATION_PLAYING_MYMOVE:
3732       case RELATION_PLAYING_NOTMYMOVE:
3733         newGameMode =
3734           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3735             IcsPlayingWhite : IcsPlayingBlack;
3736         break;
3737       case RELATION_EXAMINING:
3738         newGameMode = IcsExamining;
3739         break;
3740       case RELATION_ISOLATED_BOARD:
3741       default:
3742         /* Just display this board.  If user was doing something else,
3743            we will forget about it until the next board comes. */ 
3744         newGameMode = IcsIdle;
3745         break;
3746       case RELATION_STARTING_POSITION:
3747         newGameMode = gameMode;
3748         break;
3749     }
3750     
3751     /* Modify behavior for initial board display on move listing
3752        of wild games.
3753        */
3754     switch (ics_getting_history) {
3755       case H_FALSE:
3756       case H_REQUESTED:
3757         break;
3758       case H_GOT_REQ_HEADER:
3759       case H_GOT_UNREQ_HEADER:
3760         /* This is the initial position of the current game */
3761         gamenum = ics_gamenum;
3762         moveNum = 0;            /* old ICS bug workaround */
3763         if (to_play == 'B') {
3764           startedFromSetupPosition = TRUE;
3765           blackPlaysFirst = TRUE;
3766           moveNum = 1;
3767           if (forwardMostMove == 0) forwardMostMove = 1;
3768           if (backwardMostMove == 0) backwardMostMove = 1;
3769           if (currentMove == 0) currentMove = 1;
3770         }
3771         newGameMode = gameMode;
3772         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3773         break;
3774       case H_GOT_UNWANTED_HEADER:
3775         /* This is an initial board that we don't want */
3776         return;
3777       case H_GETTING_MOVES:
3778         /* Should not happen */
3779         DisplayError(_("Error gathering move list: extra board"), 0);
3780         ics_getting_history = H_FALSE;
3781         return;
3782     }
3783
3784    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3785                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3786      /* [HGM] We seem to have switched variant unexpectedly
3787       * Try to guess new variant from board size
3788       */
3789           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3790           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3791           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3792           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3793           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3794           if(!weird) newVariant = VariantNormal;
3795           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3796           /* Get a move list just to see the header, which
3797              will tell us whether this is really bug or zh */
3798           if (ics_getting_history == H_FALSE) {
3799             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3800             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3801             SendToICS(str);
3802           }
3803     }
3804     
3805     /* Take action if this is the first board of a new game, or of a
3806        different game than is currently being displayed.  */
3807     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3808         relation == RELATION_ISOLATED_BOARD) {
3809         
3810         /* Forget the old game and get the history (if any) of the new one */
3811         if (gameMode != BeginningOfGame) {
3812           Reset(TRUE, TRUE);
3813         }
3814         newGame = TRUE;
3815         if (appData.autoRaiseBoard) BoardToTop();
3816         prevMove = -3;
3817         if (gamenum == -1) {
3818             newGameMode = IcsIdle;
3819         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3820                    appData.getMoveList && !reqFlag) {
3821             /* Need to get game history */
3822             ics_getting_history = H_REQUESTED;
3823             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3824             SendToICS(str);
3825         }
3826         
3827         /* Initially flip the board to have black on the bottom if playing
3828            black or if the ICS flip flag is set, but let the user change
3829            it with the Flip View button. */
3830         flipView = appData.autoFlipView ? 
3831           (newGameMode == IcsPlayingBlack) || ics_flip :
3832           appData.flipView;
3833         
3834         /* Done with values from previous mode; copy in new ones */
3835         gameMode = newGameMode;
3836         ModeHighlight();
3837         ics_gamenum = gamenum;
3838         if (gamenum == gs_gamenum) {
3839             int klen = strlen(gs_kind);
3840             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3841             sprintf(str, "ICS %s", gs_kind);
3842             gameInfo.event = StrSave(str);
3843         } else {
3844             gameInfo.event = StrSave("ICS game");
3845         }
3846         gameInfo.site = StrSave(appData.icsHost);
3847         gameInfo.date = PGNDate();
3848         gameInfo.round = StrSave("-");
3849         gameInfo.white = StrSave(white);
3850         gameInfo.black = StrSave(black);
3851         timeControl = basetime * 60 * 1000;
3852         timeControl_2 = 0;
3853         timeIncrement = increment * 1000;
3854         movesPerSession = 0;
3855         gameInfo.timeControl = TimeControlTagValue();
3856         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3857   if (appData.debugMode) {
3858     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3859     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3860     setbuf(debugFP, NULL);
3861   }
3862
3863         gameInfo.outOfBook = NULL;
3864         
3865         /* Do we have the ratings? */
3866         if (strcmp(player1Name, white) == 0 &&
3867             strcmp(player2Name, black) == 0) {
3868             if (appData.debugMode)
3869               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3870                       player1Rating, player2Rating);
3871             gameInfo.whiteRating = player1Rating;
3872             gameInfo.blackRating = player2Rating;
3873         } else if (strcmp(player2Name, white) == 0 &&
3874                    strcmp(player1Name, black) == 0) {
3875             if (appData.debugMode)
3876               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3877                       player2Rating, player1Rating);
3878             gameInfo.whiteRating = player2Rating;
3879             gameInfo.blackRating = player1Rating;
3880         }
3881         player1Name[0] = player2Name[0] = NULLCHAR;
3882
3883         /* Silence shouts if requested */
3884         if (appData.quietPlay &&
3885             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3886             SendToICS(ics_prefix);
3887             SendToICS("set shout 0\n");
3888         }
3889     }
3890     
3891     /* Deal with midgame name changes */
3892     if (!newGame) {
3893         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3894             if (gameInfo.white) free(gameInfo.white);
3895             gameInfo.white = StrSave(white);
3896         }
3897         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3898             if (gameInfo.black) free(gameInfo.black);
3899             gameInfo.black = StrSave(black);
3900         }
3901     }
3902     
3903     /* Throw away game result if anything actually changes in examine mode */
3904     if (gameMode == IcsExamining && !newGame) {
3905         gameInfo.result = GameUnfinished;
3906         if (gameInfo.resultDetails != NULL) {
3907             free(gameInfo.resultDetails);
3908             gameInfo.resultDetails = NULL;
3909         }
3910     }
3911     
3912     /* In pausing && IcsExamining mode, we ignore boards coming
3913        in if they are in a different variation than we are. */
3914     if (pauseExamInvalid) return;
3915     if (pausing && gameMode == IcsExamining) {
3916         if (moveNum <= pauseExamForwardMostMove) {
3917             pauseExamInvalid = TRUE;
3918             forwardMostMove = pauseExamForwardMostMove;
3919             return;
3920         }
3921     }
3922     
3923   if (appData.debugMode) {
3924     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3925   }
3926     /* Parse the board */
3927     for (k = 0; k < ranks; k++) {
3928       for (j = 0; j < files; j++)
3929         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3930       if(gameInfo.holdingsWidth > 1) {
3931            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3932            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3933       }
3934     }
3935     CopyBoard(boards[moveNum], board);
3936     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3937     if (moveNum == 0) {
3938         startedFromSetupPosition =
3939           !CompareBoards(board, initialPosition);
3940         if(startedFromSetupPosition)
3941             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3942     }
3943
3944     /* [HGM] Set castling rights. Take the outermost Rooks,
3945        to make it also work for FRC opening positions. Note that board12
3946        is really defective for later FRC positions, as it has no way to
3947        indicate which Rook can castle if they are on the same side of King.
3948        For the initial position we grant rights to the outermost Rooks,
3949        and remember thos rights, and we then copy them on positions
3950        later in an FRC game. This means WB might not recognize castlings with
3951        Rooks that have moved back to their original position as illegal,
3952        but in ICS mode that is not its job anyway.
3953     */
3954     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3955     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3956
3957         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3958             if(board[0][i] == WhiteRook) j = i;
3959         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3960         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3961             if(board[0][i] == WhiteRook) j = i;
3962         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3963         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3964             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3965         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3966         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3967             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3968         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3969
3970         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3971         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3972             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3973         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3974             if(board[BOARD_HEIGHT-1][k] == bKing)
3975                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3976         if(gameInfo.variant == VariantTwoKings) {
3977             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3978             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3979             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3980         }
3981     } else { int r;
3982         r = boards[moveNum][CASTLING][0] = initialRights[0];
3983         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3984         r = boards[moveNum][CASTLING][1] = initialRights[1];
3985         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3986         r = boards[moveNum][CASTLING][3] = initialRights[3];
3987         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3988         r = boards[moveNum][CASTLING][4] = initialRights[4];
3989         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3990         /* wildcastle kludge: always assume King has rights */
3991         r = boards[moveNum][CASTLING][2] = initialRights[2];
3992         r = boards[moveNum][CASTLING][5] = initialRights[5];
3993     }
3994     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3995     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3996
3997     
3998     if (ics_getting_history == H_GOT_REQ_HEADER ||
3999         ics_getting_history == H_GOT_UNREQ_HEADER) {
4000         /* This was an initial position from a move list, not
4001            the current position */
4002         return;
4003     }
4004     
4005     /* Update currentMove and known move number limits */
4006     newMove = newGame || moveNum > forwardMostMove;
4007
4008     if (newGame) {
4009         forwardMostMove = backwardMostMove = currentMove = moveNum;
4010         if (gameMode == IcsExamining && moveNum == 0) {
4011           /* Workaround for ICS limitation: we are not told the wild
4012              type when starting to examine a game.  But if we ask for
4013              the move list, the move list header will tell us */
4014             ics_getting_history = H_REQUESTED;
4015             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4016             SendToICS(str);
4017         }
4018     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4019                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4020 #if ZIPPY
4021         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4022         /* [HGM] applied this also to an engine that is silently watching        */
4023         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4024             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4025             gameInfo.variant == currentlyInitializedVariant) {
4026           takeback = forwardMostMove - moveNum;
4027           for (i = 0; i < takeback; i++) {
4028             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4029             SendToProgram("undo\n", &first);
4030           }
4031         }
4032 #endif
4033
4034         forwardMostMove = moveNum;
4035         if (!pausing || currentMove > forwardMostMove)
4036           currentMove = forwardMostMove;
4037     } else {
4038         /* New part of history that is not contiguous with old part */ 
4039         if (pausing && gameMode == IcsExamining) {
4040             pauseExamInvalid = TRUE;
4041             forwardMostMove = pauseExamForwardMostMove;
4042             return;
4043         }
4044         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4045 #if ZIPPY
4046             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4047                 // [HGM] when we will receive the move list we now request, it will be
4048                 // fed to the engine from the first move on. So if the engine is not
4049                 // in the initial position now, bring it there.
4050                 InitChessProgram(&first, 0);
4051             }
4052 #endif
4053             ics_getting_history = H_REQUESTED;
4054             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4055             SendToICS(str);
4056         }
4057         forwardMostMove = backwardMostMove = currentMove = moveNum;
4058     }
4059     
4060     /* Update the clocks */
4061     if (strchr(elapsed_time, '.')) {
4062       /* Time is in ms */
4063       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4064       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4065     } else {
4066       /* Time is in seconds */
4067       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4068       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4069     }
4070       
4071
4072 #if ZIPPY
4073     if (appData.zippyPlay && newGame &&
4074         gameMode != IcsObserving && gameMode != IcsIdle &&
4075         gameMode != IcsExamining)
4076       ZippyFirstBoard(moveNum, basetime, increment);
4077 #endif
4078     
4079     /* Put the move on the move list, first converting
4080        to canonical algebraic form. */
4081     if (moveNum > 0) {
4082   if (appData.debugMode) {
4083     if (appData.debugMode) { int f = forwardMostMove;
4084         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4085                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4086                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4087     }
4088     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4089     fprintf(debugFP, "moveNum = %d\n", moveNum);
4090     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4091     setbuf(debugFP, NULL);
4092   }
4093         if (moveNum <= backwardMostMove) {
4094             /* We don't know what the board looked like before
4095                this move.  Punt. */
4096             strcpy(parseList[moveNum - 1], move_str);
4097             strcat(parseList[moveNum - 1], " ");
4098             strcat(parseList[moveNum - 1], elapsed_time);
4099             moveList[moveNum - 1][0] = NULLCHAR;
4100         } else if (strcmp(move_str, "none") == 0) {
4101             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4102             /* Again, we don't know what the board looked like;
4103                this is really the start of the game. */
4104             parseList[moveNum - 1][0] = NULLCHAR;
4105             moveList[moveNum - 1][0] = NULLCHAR;
4106             backwardMostMove = moveNum;
4107             startedFromSetupPosition = TRUE;
4108             fromX = fromY = toX = toY = -1;
4109         } else {
4110           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4111           //                 So we parse the long-algebraic move string in stead of the SAN move
4112           int valid; char buf[MSG_SIZ], *prom;
4113
4114           // str looks something like "Q/a1-a2"; kill the slash
4115           if(str[1] == '/') 
4116                 sprintf(buf, "%c%s", str[0], str+2);
4117           else  strcpy(buf, str); // might be castling
4118           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4119                 strcat(buf, prom); // long move lacks promo specification!
4120           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4121                 if(appData.debugMode) 
4122                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4123                 strcpy(move_str, buf);
4124           }
4125           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4126                                 &fromX, &fromY, &toX, &toY, &promoChar)
4127                || ParseOneMove(buf, moveNum - 1, &moveType,
4128                                 &fromX, &fromY, &toX, &toY, &promoChar);
4129           // end of long SAN patch
4130           if (valid) {
4131             (void) CoordsToAlgebraic(boards[moveNum - 1],
4132                                      PosFlags(moveNum - 1),
4133                                      fromY, fromX, toY, toX, promoChar,
4134                                      parseList[moveNum-1]);
4135             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4136               case MT_NONE:
4137               case MT_STALEMATE:
4138               default:
4139                 break;
4140               case MT_CHECK:
4141                 if(gameInfo.variant != VariantShogi)
4142                     strcat(parseList[moveNum - 1], "+");
4143                 break;
4144               case MT_CHECKMATE:
4145               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4146                 strcat(parseList[moveNum - 1], "#");
4147                 break;
4148             }
4149             strcat(parseList[moveNum - 1], " ");
4150             strcat(parseList[moveNum - 1], elapsed_time);
4151             /* currentMoveString is set as a side-effect of ParseOneMove */
4152             strcpy(moveList[moveNum - 1], currentMoveString);
4153             strcat(moveList[moveNum - 1], "\n");
4154           } else {
4155             /* Move from ICS was illegal!?  Punt. */
4156   if (appData.debugMode) {
4157     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4158     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4159   }
4160             strcpy(parseList[moveNum - 1], move_str);
4161             strcat(parseList[moveNum - 1], " ");
4162             strcat(parseList[moveNum - 1], elapsed_time);
4163             moveList[moveNum - 1][0] = NULLCHAR;
4164             fromX = fromY = toX = toY = -1;
4165           }
4166         }
4167   if (appData.debugMode) {
4168     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4169     setbuf(debugFP, NULL);
4170   }
4171
4172 #if ZIPPY
4173         /* Send move to chess program (BEFORE animating it). */
4174         if (appData.zippyPlay && !newGame && newMove && 
4175            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4176
4177             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4178                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4179                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4180                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4181                             move_str);
4182                     DisplayError(str, 0);
4183                 } else {
4184                     if (first.sendTime) {
4185                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4186                     }
4187                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4188                     if (firstMove && !bookHit) {
4189                         firstMove = FALSE;
4190                         if (first.useColors) {
4191                           SendToProgram(gameMode == IcsPlayingWhite ?
4192                                         "white\ngo\n" :
4193                                         "black\ngo\n", &first);
4194                         } else {
4195                           SendToProgram("go\n", &first);
4196                         }
4197                         first.maybeThinking = TRUE;
4198                     }
4199                 }
4200             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4201               if (moveList[moveNum - 1][0] == NULLCHAR) {
4202                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4203                 DisplayError(str, 0);
4204               } else {
4205                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4206                 SendMoveToProgram(moveNum - 1, &first);
4207               }
4208             }
4209         }
4210 #endif
4211     }
4212
4213     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4214         /* If move comes from a remote source, animate it.  If it
4215            isn't remote, it will have already been animated. */
4216         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4217             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4218         }
4219         if (!pausing && appData.highlightLastMove) {
4220             SetHighlights(fromX, fromY, toX, toY);
4221         }
4222     }
4223     
4224     /* Start the clocks */
4225     whiteFlag = blackFlag = FALSE;
4226     appData.clockMode = !(basetime == 0 && increment == 0);
4227     if (ticking == 0) {
4228       ics_clock_paused = TRUE;
4229       StopClocks();
4230     } else if (ticking == 1) {
4231       ics_clock_paused = FALSE;
4232     }
4233     if (gameMode == IcsIdle ||
4234         relation == RELATION_OBSERVING_STATIC ||
4235         relation == RELATION_EXAMINING ||
4236         ics_clock_paused)
4237       DisplayBothClocks();
4238     else
4239       StartClocks();
4240     
4241     /* Display opponents and material strengths */
4242     if (gameInfo.variant != VariantBughouse &&
4243         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4244         if (tinyLayout || smallLayout) {
4245             if(gameInfo.variant == VariantNormal)
4246                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4247                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4248                     basetime, increment);
4249             else
4250                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4251                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4252                     basetime, increment, (int) gameInfo.variant);
4253         } else {
4254             if(gameInfo.variant == VariantNormal)
4255                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4256                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4257                     basetime, increment);
4258             else
4259                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4260                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4261                     basetime, increment, VariantName(gameInfo.variant));
4262         }
4263         DisplayTitle(str);
4264   if (appData.debugMode) {
4265     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4266   }
4267     }
4268
4269
4270     /* Display the board */
4271     if (!pausing && !appData.noGUI) {
4272       
4273       if (appData.premove)
4274           if (!gotPremove || 
4275              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4276              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4277               ClearPremoveHighlights();
4278
4279       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4280       DrawPosition(j, boards[currentMove]);
4281
4282       DisplayMove(moveNum - 1);
4283       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4284             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4285               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4286         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4287       }
4288     }
4289
4290     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4291 #if ZIPPY
4292     if(bookHit) { // [HGM] book: simulate book reply
4293         static char bookMove[MSG_SIZ]; // a bit generous?
4294
4295         programStats.nodes = programStats.depth = programStats.time = 
4296         programStats.score = programStats.got_only_move = 0;
4297         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4298
4299         strcpy(bookMove, "move ");
4300         strcat(bookMove, bookHit);
4301         HandleMachineMove(bookMove, &first);
4302     }
4303 #endif
4304 }
4305
4306 void
4307 GetMoveListEvent()
4308 {
4309     char buf[MSG_SIZ];
4310     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4311         ics_getting_history = H_REQUESTED;
4312         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4313         SendToICS(buf);
4314     }
4315 }
4316
4317 void
4318 AnalysisPeriodicEvent(force)
4319      int force;
4320 {
4321     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4322          && !force) || !appData.periodicUpdates)
4323       return;
4324
4325     /* Send . command to Crafty to collect stats */
4326     SendToProgram(".\n", &first);
4327
4328     /* Don't send another until we get a response (this makes
4329        us stop sending to old Crafty's which don't understand
4330        the "." command (sending illegal cmds resets node count & time,
4331        which looks bad)) */
4332     programStats.ok_to_send = 0;
4333 }
4334
4335 void ics_update_width(new_width)
4336         int new_width;
4337 {
4338         ics_printf("set width %d\n", new_width);
4339 }
4340
4341 void
4342 SendMoveToProgram(moveNum, cps)
4343      int moveNum;
4344      ChessProgramState *cps;
4345 {
4346     char buf[MSG_SIZ];
4347
4348     if (cps->useUsermove) {
4349       SendToProgram("usermove ", cps);
4350     }
4351     if (cps->useSAN) {
4352       char *space;
4353       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4354         int len = space - parseList[moveNum];
4355         memcpy(buf, parseList[moveNum], len);
4356         buf[len++] = '\n';
4357         buf[len] = NULLCHAR;
4358       } else {
4359         sprintf(buf, "%s\n", parseList[moveNum]);
4360       }
4361       SendToProgram(buf, cps);
4362     } else {
4363       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4364         AlphaRank(moveList[moveNum], 4);
4365         SendToProgram(moveList[moveNum], cps);
4366         AlphaRank(moveList[moveNum], 4); // and back
4367       } else
4368       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4369        * the engine. It would be nice to have a better way to identify castle 
4370        * moves here. */
4371       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4372                                                                          && cps->useOOCastle) {
4373         int fromX = moveList[moveNum][0] - AAA; 
4374         int fromY = moveList[moveNum][1] - ONE;
4375         int toX = moveList[moveNum][2] - AAA; 
4376         int toY = moveList[moveNum][3] - ONE;
4377         if((boards[moveNum][fromY][fromX] == WhiteKing 
4378             && boards[moveNum][toY][toX] == WhiteRook)
4379            || (boards[moveNum][fromY][fromX] == BlackKing 
4380                && boards[moveNum][toY][toX] == BlackRook)) {
4381           if(toX > fromX) SendToProgram("O-O\n", cps);
4382           else SendToProgram("O-O-O\n", cps);
4383         }
4384         else SendToProgram(moveList[moveNum], cps);
4385       }
4386       else SendToProgram(moveList[moveNum], cps);
4387       /* End of additions by Tord */
4388     }
4389
4390     /* [HGM] setting up the opening has brought engine in force mode! */
4391     /*       Send 'go' if we are in a mode where machine should play. */
4392     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4393         (gameMode == TwoMachinesPlay   ||
4394 #ifdef ZIPPY
4395          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4396 #endif
4397          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4398         SendToProgram("go\n", cps);
4399   if (appData.debugMode) {
4400     fprintf(debugFP, "(extra)\n");
4401   }
4402     }
4403     setboardSpoiledMachineBlack = 0;
4404 }
4405
4406 void
4407 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4408      ChessMove moveType;
4409      int fromX, fromY, toX, toY;
4410 {
4411     char user_move[MSG_SIZ];
4412
4413     switch (moveType) {
4414       default:
4415         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4416                 (int)moveType, fromX, fromY, toX, toY);
4417         DisplayError(user_move + strlen("say "), 0);
4418         break;
4419       case WhiteKingSideCastle:
4420       case BlackKingSideCastle:
4421       case WhiteQueenSideCastleWild:
4422       case BlackQueenSideCastleWild:
4423       /* PUSH Fabien */
4424       case WhiteHSideCastleFR:
4425       case BlackHSideCastleFR:
4426       /* POP Fabien */
4427         sprintf(user_move, "o-o\n");
4428         break;
4429       case WhiteQueenSideCastle:
4430       case BlackQueenSideCastle:
4431       case WhiteKingSideCastleWild:
4432       case BlackKingSideCastleWild:
4433       /* PUSH Fabien */
4434       case WhiteASideCastleFR:
4435       case BlackASideCastleFR:
4436       /* POP Fabien */
4437         sprintf(user_move, "o-o-o\n");
4438         break;
4439       case WhitePromotionQueen:
4440       case BlackPromotionQueen:
4441       case WhitePromotionRook:
4442       case BlackPromotionRook:
4443       case WhitePromotionBishop:
4444       case BlackPromotionBishop:
4445       case WhitePromotionKnight:
4446       case BlackPromotionKnight:
4447       case WhitePromotionKing:
4448       case BlackPromotionKing:
4449       case WhitePromotionChancellor:
4450       case BlackPromotionChancellor:
4451       case WhitePromotionArchbishop:
4452       case BlackPromotionArchbishop:
4453         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4454             sprintf(user_move, "%c%c%c%c=%c\n",
4455                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4456                 PieceToChar(WhiteFerz));
4457         else if(gameInfo.variant == VariantGreat)
4458             sprintf(user_move, "%c%c%c%c=%c\n",
4459                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4460                 PieceToChar(WhiteMan));
4461         else
4462             sprintf(user_move, "%c%c%c%c=%c\n",
4463                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4464                 PieceToChar(PromoPiece(moveType)));
4465         break;
4466       case WhiteDrop:
4467       case BlackDrop:
4468         sprintf(user_move, "%c@%c%c\n",
4469                 ToUpper(PieceToChar((ChessSquare) fromX)),
4470                 AAA + toX, ONE + toY);
4471         break;
4472       case NormalMove:
4473       case WhiteCapturesEnPassant:
4474       case BlackCapturesEnPassant:
4475       case IllegalMove:  /* could be a variant we don't quite understand */
4476         sprintf(user_move, "%c%c%c%c\n",
4477                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4478         break;
4479     }
4480     SendToICS(user_move);
4481     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4482         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4483 }
4484
4485 void
4486 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4487      int rf, ff, rt, ft;
4488      char promoChar;
4489      char move[7];
4490 {
4491     if (rf == DROP_RANK) {
4492         sprintf(move, "%c@%c%c\n",
4493                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4494     } else {
4495         if (promoChar == 'x' || promoChar == NULLCHAR) {
4496             sprintf(move, "%c%c%c%c\n",
4497                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4498         } else {
4499             sprintf(move, "%c%c%c%c%c\n",
4500                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4501         }
4502     }
4503 }
4504
4505 void
4506 ProcessICSInitScript(f)
4507      FILE *f;
4508 {
4509     char buf[MSG_SIZ];
4510
4511     while (fgets(buf, MSG_SIZ, f)) {
4512         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4513     }
4514
4515     fclose(f);
4516 }
4517
4518
4519 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4520 void
4521 AlphaRank(char *move, int n)
4522 {
4523 //    char *p = move, c; int x, y;
4524
4525     if (appData.debugMode) {
4526         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4527     }
4528
4529     if(move[1]=='*' && 
4530        move[2]>='0' && move[2]<='9' &&
4531        move[3]>='a' && move[3]<='x'    ) {
4532         move[1] = '@';
4533         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4534         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4535     } else
4536     if(move[0]>='0' && move[0]<='9' &&
4537        move[1]>='a' && move[1]<='x' &&
4538        move[2]>='0' && move[2]<='9' &&
4539        move[3]>='a' && move[3]<='x'    ) {
4540         /* input move, Shogi -> normal */
4541         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4542         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4543         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4544         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4545     } else
4546     if(move[1]=='@' &&
4547        move[3]>='0' && move[3]<='9' &&
4548        move[2]>='a' && move[2]<='x'    ) {
4549         move[1] = '*';
4550         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4551         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4552     } else
4553     if(
4554        move[0]>='a' && move[0]<='x' &&
4555        move[3]>='0' && move[3]<='9' &&
4556        move[2]>='a' && move[2]<='x'    ) {
4557          /* output move, normal -> Shogi */
4558         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4559         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4560         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4561         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4562         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4563     }
4564     if (appData.debugMode) {
4565         fprintf(debugFP, "   out = '%s'\n", move);
4566     }
4567 }
4568
4569 /* Parser for moves from gnuchess, ICS, or user typein box */
4570 Boolean
4571 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4572      char *move;
4573      int moveNum;
4574      ChessMove *moveType;
4575      int *fromX, *fromY, *toX, *toY;
4576      char *promoChar;
4577 {       
4578     if (appData.debugMode) {
4579         fprintf(debugFP, "move to parse: %s\n", move);
4580     }
4581     *moveType = yylexstr(moveNum, move);
4582
4583     switch (*moveType) {
4584       case WhitePromotionChancellor:
4585       case BlackPromotionChancellor:
4586       case WhitePromotionArchbishop:
4587       case BlackPromotionArchbishop:
4588       case WhitePromotionQueen:
4589       case BlackPromotionQueen:
4590       case WhitePromotionRook:
4591       case BlackPromotionRook:
4592       case WhitePromotionBishop:
4593       case BlackPromotionBishop:
4594       case WhitePromotionKnight:
4595       case BlackPromotionKnight:
4596       case WhitePromotionKing:
4597       case BlackPromotionKing:
4598       case NormalMove:
4599       case WhiteCapturesEnPassant:
4600       case BlackCapturesEnPassant:
4601       case WhiteKingSideCastle:
4602       case WhiteQueenSideCastle:
4603       case BlackKingSideCastle:
4604       case BlackQueenSideCastle:
4605       case WhiteKingSideCastleWild:
4606       case WhiteQueenSideCastleWild:
4607       case BlackKingSideCastleWild:
4608       case BlackQueenSideCastleWild:
4609       /* Code added by Tord: */
4610       case WhiteHSideCastleFR:
4611       case WhiteASideCastleFR:
4612       case BlackHSideCastleFR:
4613       case BlackASideCastleFR:
4614       /* End of code added by Tord */
4615       case IllegalMove:         /* bug or odd chess variant */
4616         *fromX = currentMoveString[0] - AAA;
4617         *fromY = currentMoveString[1] - ONE;
4618         *toX = currentMoveString[2] - AAA;
4619         *toY = currentMoveString[3] - ONE;
4620         *promoChar = currentMoveString[4];
4621         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4622             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4623     if (appData.debugMode) {
4624         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4625     }
4626             *fromX = *fromY = *toX = *toY = 0;
4627             return FALSE;
4628         }
4629         if (appData.testLegality) {
4630           return (*moveType != IllegalMove);
4631         } else {
4632           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4633                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4634         }
4635
4636       case WhiteDrop:
4637       case BlackDrop:
4638         *fromX = *moveType == WhiteDrop ?
4639           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4640           (int) CharToPiece(ToLower(currentMoveString[0]));
4641         *fromY = DROP_RANK;
4642         *toX = currentMoveString[2] - AAA;
4643         *toY = currentMoveString[3] - ONE;
4644         *promoChar = NULLCHAR;
4645         return TRUE;
4646
4647       case AmbiguousMove:
4648       case ImpossibleMove:
4649       case (ChessMove) 0:       /* end of file */
4650       case ElapsedTime:
4651       case Comment:
4652       case PGNTag:
4653       case NAG:
4654       case WhiteWins:
4655       case BlackWins:
4656       case GameIsDrawn:
4657       default:
4658     if (appData.debugMode) {
4659         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4660     }
4661         /* bug? */
4662         *fromX = *fromY = *toX = *toY = 0;
4663         *promoChar = NULLCHAR;
4664         return FALSE;
4665     }
4666 }
4667
4668
4669 void
4670 ParsePV(char *pv)
4671 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4672   int fromX, fromY, toX, toY; char promoChar;
4673   ChessMove moveType;
4674   Boolean valid;
4675   int nr = 0;
4676
4677   endPV = forwardMostMove;
4678   do {
4679     while(*pv == ' ') pv++;
4680     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4681     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4682 if(appData.debugMode){
4683 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4684 }
4685     if(!valid && nr == 0 &&
4686        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4687         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4688     }
4689     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4690     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4691     nr++;
4692     if(endPV+1 > framePtr) break; // no space, truncate
4693     if(!valid) break;
4694     endPV++;
4695     CopyBoard(boards[endPV], boards[endPV-1]);
4696     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4697     moveList[endPV-1][0] = fromX + AAA;
4698     moveList[endPV-1][1] = fromY + ONE;
4699     moveList[endPV-1][2] = toX + AAA;
4700     moveList[endPV-1][3] = toY + ONE;
4701     parseList[endPV-1][0] = NULLCHAR;
4702   } while(valid);
4703   currentMove = endPV;
4704   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4705   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4706                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4707   DrawPosition(TRUE, boards[currentMove]);
4708 }
4709
4710 static int lastX, lastY;
4711
4712 Boolean
4713 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4714 {
4715         int startPV;
4716
4717         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4718         lastX = x; lastY = y;
4719         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4720         startPV = index;
4721       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4722       index = startPV;
4723         while(buf[index] && buf[index] != '\n') index++;
4724         buf[index] = 0;
4725         ParsePV(buf+startPV);
4726         *start = startPV; *end = index-1;
4727         return TRUE;
4728 }
4729
4730 Boolean
4731 LoadPV(int x, int y)
4732 { // called on right mouse click to load PV
4733   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4734   lastX = x; lastY = y;
4735   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4736   return TRUE;
4737 }
4738
4739 void
4740 UnLoadPV()
4741 {
4742   if(endPV < 0) return;
4743   endPV = -1;
4744   currentMove = forwardMostMove;
4745   ClearPremoveHighlights();
4746   DrawPosition(TRUE, boards[currentMove]);
4747 }
4748
4749 void
4750 MovePV(int x, int y, int h)
4751 { // step through PV based on mouse coordinates (called on mouse move)
4752   int margin = h>>3, step = 0;
4753
4754   if(endPV < 0) return;
4755   // we must somehow check if right button is still down (might be released off board!)
4756   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4757   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4758   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4759   if(!step) return;
4760   lastX = x; lastY = y;
4761   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4762   currentMove += step;
4763   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4764   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4765                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4766   DrawPosition(FALSE, boards[currentMove]);
4767 }
4768
4769
4770 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4771 // All positions will have equal probability, but the current method will not provide a unique
4772 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4773 #define DARK 1
4774 #define LITE 2
4775 #define ANY 3
4776
4777 int squaresLeft[4];
4778 int piecesLeft[(int)BlackPawn];
4779 int seed, nrOfShuffles;
4780
4781 void GetPositionNumber()
4782 {       // sets global variable seed
4783         int i;
4784
4785         seed = appData.defaultFrcPosition;
4786         if(seed < 0) { // randomize based on time for negative FRC position numbers
4787                 for(i=0; i<50; i++) seed += random();
4788                 seed = random() ^ random() >> 8 ^ random() << 8;
4789                 if(seed<0) seed = -seed;
4790         }
4791 }
4792
4793 int put(Board board, int pieceType, int rank, int n, int shade)
4794 // put the piece on the (n-1)-th empty squares of the given shade
4795 {
4796         int i;
4797
4798         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4799                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4800                         board[rank][i] = (ChessSquare) pieceType;
4801                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4802                         squaresLeft[ANY]--;
4803                         piecesLeft[pieceType]--; 
4804                         return i;
4805                 }
4806         }
4807         return -1;
4808 }
4809
4810
4811 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4812 // calculate where the next piece goes, (any empty square), and put it there
4813 {
4814         int i;
4815
4816         i = seed % squaresLeft[shade];
4817         nrOfShuffles *= squaresLeft[shade];
4818         seed /= squaresLeft[shade];
4819         put(board, pieceType, rank, i, shade);
4820 }
4821
4822 void AddTwoPieces(Board board, int pieceType, int rank)
4823 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4824 {
4825         int i, n=squaresLeft[ANY], j=n-1, k;
4826
4827         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4828         i = seed % k;  // pick one
4829         nrOfShuffles *= k;
4830         seed /= k;
4831         while(i >= j) i -= j--;
4832         j = n - 1 - j; i += j;
4833         put(board, pieceType, rank, j, ANY);
4834         put(board, pieceType, rank, i, ANY);
4835 }
4836
4837 void SetUpShuffle(Board board, int number)
4838 {
4839         int i, p, first=1;
4840
4841         GetPositionNumber(); nrOfShuffles = 1;
4842
4843         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4844         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4845         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4846
4847         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4848
4849         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4850             p = (int) board[0][i];
4851             if(p < (int) BlackPawn) piecesLeft[p] ++;
4852             board[0][i] = EmptySquare;
4853         }
4854
4855         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4856             // shuffles restricted to allow normal castling put KRR first
4857             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4858                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4859             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4860                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4861             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4862                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4863             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4864                 put(board, WhiteRook, 0, 0, ANY);
4865             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4866         }
4867
4868         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4869             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4870             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4871                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4872                 while(piecesLeft[p] >= 2) {
4873                     AddOnePiece(board, p, 0, LITE);
4874                     AddOnePiece(board, p, 0, DARK);
4875                 }
4876                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4877             }
4878
4879         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4880             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4881             // but we leave King and Rooks for last, to possibly obey FRC restriction
4882             if(p == (int)WhiteRook) continue;
4883             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4884             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4885         }
4886
4887         // now everything is placed, except perhaps King (Unicorn) and Rooks
4888
4889         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4890             // Last King gets castling rights
4891             while(piecesLeft[(int)WhiteUnicorn]) {
4892                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4893                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4894             }
4895
4896             while(piecesLeft[(int)WhiteKing]) {
4897                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4898                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4899             }
4900
4901
4902         } else {
4903             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4904             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4905         }
4906
4907         // Only Rooks can be left; simply place them all
4908         while(piecesLeft[(int)WhiteRook]) {
4909                 i = put(board, WhiteRook, 0, 0, ANY);
4910                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4911                         if(first) {
4912                                 first=0;
4913                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4914                         }
4915                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4916                 }
4917         }
4918         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4919             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4920         }
4921
4922         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4923 }
4924
4925 int SetCharTable( char *table, const char * map )
4926 /* [HGM] moved here from winboard.c because of its general usefulness */
4927 /*       Basically a safe strcpy that uses the last character as King */
4928 {
4929     int result = FALSE; int NrPieces;
4930
4931     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4932                     && NrPieces >= 12 && !(NrPieces&1)) {
4933         int i; /* [HGM] Accept even length from 12 to 34 */
4934
4935         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4936         for( i=0; i<NrPieces/2-1; i++ ) {
4937             table[i] = map[i];
4938             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4939         }
4940         table[(int) WhiteKing]  = map[NrPieces/2-1];
4941         table[(int) BlackKing]  = map[NrPieces-1];
4942
4943         result = TRUE;
4944     }
4945
4946     return result;
4947 }
4948
4949 void Prelude(Board board)
4950 {       // [HGM] superchess: random selection of exo-pieces
4951         int i, j, k; ChessSquare p; 
4952         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4953
4954         GetPositionNumber(); // use FRC position number
4955
4956         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4957             SetCharTable(pieceToChar, appData.pieceToCharTable);
4958             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4959                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4960         }
4961
4962         j = seed%4;                 seed /= 4; 
4963         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4964         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4965         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4966         j = seed%3 + (seed%3 >= j); seed /= 3; 
4967         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4968         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4969         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4970         j = seed%3;                 seed /= 3; 
4971         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4972         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4973         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4974         j = seed%2 + (seed%2 >= j); seed /= 2; 
4975         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
4977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
4978         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
4979         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
4980         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4981         put(board, exoPieces[0],    0, 0, ANY);
4982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4983 }
4984
4985 void
4986 InitPosition(redraw)
4987      int redraw;
4988 {
4989     ChessSquare (* pieces)[BOARD_FILES];
4990     int i, j, pawnRow, overrule,
4991     oldx = gameInfo.boardWidth,
4992     oldy = gameInfo.boardHeight,
4993     oldh = gameInfo.holdingsWidth,
4994     oldv = gameInfo.variant;
4995
4996     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4997
4998     /* [AS] Initialize pv info list [HGM] and game status */
4999     {
5000         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5001             pvInfoList[i].depth = 0;
5002             boards[i][EP_STATUS] = EP_NONE;
5003             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5004         }
5005
5006         initialRulePlies = 0; /* 50-move counter start */
5007
5008         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5009         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5010     }
5011
5012     
5013     /* [HGM] logic here is completely changed. In stead of full positions */
5014     /* the initialized data only consist of the two backranks. The switch */
5015     /* selects which one we will use, which is than copied to the Board   */
5016     /* initialPosition, which for the rest is initialized by Pawns and    */
5017     /* empty squares. This initial position is then copied to boards[0],  */
5018     /* possibly after shuffling, so that it remains available.            */
5019
5020     gameInfo.holdingsWidth = 0; /* default board sizes */
5021     gameInfo.boardWidth    = 8;
5022     gameInfo.boardHeight   = 8;
5023     gameInfo.holdingsSize  = 0;
5024     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5025     for(i=0; i<BOARD_FILES-2; i++)
5026       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5027     initialPosition[EP_STATUS] = EP_NONE;
5028     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5029
5030     switch (gameInfo.variant) {
5031     case VariantFischeRandom:
5032       shuffleOpenings = TRUE;
5033     default:
5034       pieces = FIDEArray;
5035       break;
5036     case VariantShatranj:
5037       pieces = ShatranjArray;
5038       nrCastlingRights = 0;
5039       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5040       break;
5041     case VariantMakruk:
5042       pieces = makrukArray;
5043       nrCastlingRights = 0;
5044       startedFromSetupPosition = TRUE;
5045       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5046       break;
5047     case VariantTwoKings:
5048       pieces = twoKingsArray;
5049       break;
5050     case VariantCapaRandom:
5051       shuffleOpenings = TRUE;
5052     case VariantCapablanca:
5053       pieces = CapablancaArray;
5054       gameInfo.boardWidth = 10;
5055       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5056       break;
5057     case VariantGothic:
5058       pieces = GothicArray;
5059       gameInfo.boardWidth = 10;
5060       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5061       break;
5062     case VariantJanus:
5063       pieces = JanusArray;
5064       gameInfo.boardWidth = 10;
5065       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5066       nrCastlingRights = 6;
5067         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5068         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5069         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5070         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5071         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5072         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5073       break;
5074     case VariantFalcon:
5075       pieces = FalconArray;
5076       gameInfo.boardWidth = 10;
5077       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5078       break;
5079     case VariantXiangqi:
5080       pieces = XiangqiArray;
5081       gameInfo.boardWidth  = 9;
5082       gameInfo.boardHeight = 10;
5083       nrCastlingRights = 0;
5084       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5085       break;
5086     case VariantShogi:
5087       pieces = ShogiArray;
5088       gameInfo.boardWidth  = 9;
5089       gameInfo.boardHeight = 9;
5090       gameInfo.holdingsSize = 7;
5091       nrCastlingRights = 0;
5092       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5093       break;
5094     case VariantCourier:
5095       pieces = CourierArray;
5096       gameInfo.boardWidth  = 12;
5097       nrCastlingRights = 0;
5098       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5099       break;
5100     case VariantKnightmate:
5101       pieces = KnightmateArray;
5102       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5103       break;
5104     case VariantFairy:
5105       pieces = fairyArray;
5106       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5107       break;
5108     case VariantGreat:
5109       pieces = GreatArray;
5110       gameInfo.boardWidth = 10;
5111       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5112       gameInfo.holdingsSize = 8;
5113       break;
5114     case VariantSuper:
5115       pieces = FIDEArray;
5116       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5117       gameInfo.holdingsSize = 8;
5118       startedFromSetupPosition = TRUE;
5119       break;
5120     case VariantCrazyhouse:
5121     case VariantBughouse:
5122       pieces = FIDEArray;
5123       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5124       gameInfo.holdingsSize = 5;
5125       break;
5126     case VariantWildCastle:
5127       pieces = FIDEArray;
5128       /* !!?shuffle with kings guaranteed to be on d or e file */
5129       shuffleOpenings = 1;
5130       break;
5131     case VariantNoCastle:
5132       pieces = FIDEArray;
5133       nrCastlingRights = 0;
5134       /* !!?unconstrained back-rank shuffle */
5135       shuffleOpenings = 1;
5136       break;
5137     }
5138
5139     overrule = 0;
5140     if(appData.NrFiles >= 0) {
5141         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5142         gameInfo.boardWidth = appData.NrFiles;
5143     }
5144     if(appData.NrRanks >= 0) {
5145         gameInfo.boardHeight = appData.NrRanks;
5146     }
5147     if(appData.holdingsSize >= 0) {
5148         i = appData.holdingsSize;
5149         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5150         gameInfo.holdingsSize = i;
5151     }
5152     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5153     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5154         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5155
5156     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5157     if(pawnRow < 1) pawnRow = 1;
5158     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5159
5160     /* User pieceToChar list overrules defaults */
5161     if(appData.pieceToCharTable != NULL)
5162         SetCharTable(pieceToChar, appData.pieceToCharTable);
5163
5164     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5165
5166         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5167             s = (ChessSquare) 0; /* account holding counts in guard band */
5168         for( i=0; i<BOARD_HEIGHT; i++ )
5169             initialPosition[i][j] = s;
5170
5171         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5172         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5173         initialPosition[pawnRow][j] = WhitePawn;
5174         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5175         if(gameInfo.variant == VariantXiangqi) {
5176             if(j&1) {
5177                 initialPosition[pawnRow][j] = 
5178                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5179                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5180                    initialPosition[2][j] = WhiteCannon;
5181                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5182                 }
5183             }
5184         }
5185         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5186     }
5187     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5188
5189             j=BOARD_LEFT+1;
5190             initialPosition[1][j] = WhiteBishop;
5191             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5192             j=BOARD_RGHT-2;
5193             initialPosition[1][j] = WhiteRook;
5194             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5195     }
5196
5197     if( nrCastlingRights == -1) {
5198         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5199         /*       This sets default castling rights from none to normal corners   */
5200         /* Variants with other castling rights must set them themselves above    */
5201         nrCastlingRights = 6;
5202        
5203         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5204         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5205         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5206         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5207         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5208         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5209      }
5210
5211      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5212      if(gameInfo.variant == VariantGreat) { // promotion commoners
5213         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5214         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5215         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5216         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5217      }
5218   if (appData.debugMode) {
5219     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5220   }
5221     if(shuffleOpenings) {
5222         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5223         startedFromSetupPosition = TRUE;
5224     }
5225     if(startedFromPositionFile) {
5226       /* [HGM] loadPos: use PositionFile for every new game */
5227       CopyBoard(initialPosition, filePosition);
5228       for(i=0; i<nrCastlingRights; i++)
5229           initialRights[i] = filePosition[CASTLING][i];
5230       startedFromSetupPosition = TRUE;
5231     }
5232
5233     CopyBoard(boards[0], initialPosition);
5234
5235     if(oldx != gameInfo.boardWidth ||
5236        oldy != gameInfo.boardHeight ||
5237        oldh != gameInfo.holdingsWidth
5238 #ifdef GOTHIC
5239        || oldv == VariantGothic ||        // For licensing popups
5240        gameInfo.variant == VariantGothic
5241 #endif
5242 #ifdef FALCON
5243        || oldv == VariantFalcon ||
5244        gameInfo.variant == VariantFalcon
5245 #endif
5246                                          )
5247             InitDrawingSizes(-2 ,0);
5248
5249     if (redraw)
5250       DrawPosition(TRUE, boards[currentMove]);
5251 }
5252
5253 void
5254 SendBoard(cps, moveNum)
5255      ChessProgramState *cps;
5256      int moveNum;
5257 {
5258     char message[MSG_SIZ];
5259     
5260     if (cps->useSetboard) {
5261       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5262       sprintf(message, "setboard %s\n", fen);
5263       SendToProgram(message, cps);
5264       free(fen);
5265
5266     } else {
5267       ChessSquare *bp;
5268       int i, j;
5269       /* Kludge to set black to move, avoiding the troublesome and now
5270        * deprecated "black" command.
5271        */
5272       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5273
5274       SendToProgram("edit\n", cps);
5275       SendToProgram("#\n", cps);
5276       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5277         bp = &boards[moveNum][i][BOARD_LEFT];
5278         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5279           if ((int) *bp < (int) BlackPawn) {
5280             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5281                     AAA + j, ONE + i);
5282             if(message[0] == '+' || message[0] == '~') {
5283                 sprintf(message, "%c%c%c+\n",
5284                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5285                         AAA + j, ONE + i);
5286             }
5287             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5288                 message[1] = BOARD_RGHT   - 1 - j + '1';
5289                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5290             }
5291             SendToProgram(message, cps);
5292           }
5293         }
5294       }
5295     
5296       SendToProgram("c\n", cps);
5297       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5298         bp = &boards[moveNum][i][BOARD_LEFT];
5299         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5300           if (((int) *bp != (int) EmptySquare)
5301               && ((int) *bp >= (int) BlackPawn)) {
5302             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5303                     AAA + j, ONE + i);
5304             if(message[0] == '+' || message[0] == '~') {
5305                 sprintf(message, "%c%c%c+\n",
5306                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5307                         AAA + j, ONE + i);
5308             }
5309             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5310                 message[1] = BOARD_RGHT   - 1 - j + '1';
5311                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5312             }
5313             SendToProgram(message, cps);
5314           }
5315         }
5316       }
5317     
5318       SendToProgram(".\n", cps);
5319     }
5320     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5321 }
5322
5323 int
5324 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5325 {
5326     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5327     /* [HGM] add Shogi promotions */
5328     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5329     ChessSquare piece;
5330     ChessMove moveType;
5331     Boolean premove;
5332
5333     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5334     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5335
5336     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5337       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5338         return FALSE;
5339
5340     piece = boards[currentMove][fromY][fromX];
5341     if(gameInfo.variant == VariantShogi) {
5342         promotionZoneSize = 3;
5343         highestPromotingPiece = (int)WhiteFerz;
5344     } else if(gameInfo.variant == VariantMakruk) {
5345         promotionZoneSize = 3;
5346     }
5347
5348     // next weed out all moves that do not touch the promotion zone at all
5349     if((int)piece >= BlackPawn) {
5350         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5351              return FALSE;
5352         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5353     } else {
5354         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5355            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5356     }
5357
5358     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5359
5360     // weed out mandatory Shogi promotions
5361     if(gameInfo.variant == VariantShogi) {
5362         if(piece >= BlackPawn) {
5363             if(toY == 0 && piece == BlackPawn ||
5364                toY == 0 && piece == BlackQueen ||
5365                toY <= 1 && piece == BlackKnight) {
5366                 *promoChoice = '+';
5367                 return FALSE;
5368             }
5369         } else {
5370             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5371                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5372                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5373                 *promoChoice = '+';
5374                 return FALSE;
5375             }
5376         }
5377     }
5378
5379     // weed out obviously illegal Pawn moves
5380     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5381         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5382         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5383         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5384         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5385         // note we are not allowed to test for valid (non-)capture, due to premove
5386     }
5387
5388     // we either have a choice what to promote to, or (in Shogi) whether to promote
5389     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5390         *promoChoice = PieceToChar(BlackFerz);  // no choice
5391         return FALSE;
5392     }
5393     if(appData.alwaysPromoteToQueen) { // predetermined
5394         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5395              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5396         else *promoChoice = PieceToChar(BlackQueen);
5397         return FALSE;
5398     }
5399
5400     // suppress promotion popup on illegal moves that are not premoves
5401     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5402               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5403     if(appData.testLegality && !premove) {
5404         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5405                         fromY, fromX, toY, toX, NULLCHAR);
5406         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5407            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5408             return FALSE;
5409     }
5410
5411     return TRUE;
5412 }
5413
5414 int
5415 InPalace(row, column)
5416      int row, column;
5417 {   /* [HGM] for Xiangqi */
5418     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5419          column < (BOARD_WIDTH + 4)/2 &&
5420          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5421     return FALSE;
5422 }
5423
5424 int
5425 PieceForSquare (x, y)
5426      int x;
5427      int y;
5428 {
5429   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5430      return -1;
5431   else
5432      return boards[currentMove][y][x];
5433 }
5434
5435 int
5436 OKToStartUserMove(x, y)
5437      int x, y;
5438 {
5439     ChessSquare from_piece;
5440     int white_piece;
5441
5442     if (matchMode) return FALSE;
5443     if (gameMode == EditPosition) return TRUE;
5444
5445     if (x >= 0 && y >= 0)
5446       from_piece = boards[currentMove][y][x];
5447     else
5448       from_piece = EmptySquare;
5449
5450     if (from_piece == EmptySquare) return FALSE;
5451
5452     white_piece = (int)from_piece >= (int)WhitePawn &&
5453       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5454
5455     switch (gameMode) {
5456       case PlayFromGameFile:
5457       case AnalyzeFile:
5458       case TwoMachinesPlay:
5459       case EndOfGame:
5460         return FALSE;
5461
5462       case IcsObserving:
5463       case IcsIdle:
5464         return FALSE;
5465
5466       case MachinePlaysWhite:
5467       case IcsPlayingBlack:
5468         if (appData.zippyPlay) return FALSE;
5469         if (white_piece) {
5470             DisplayMoveError(_("You are playing Black"));
5471             return FALSE;
5472         }
5473         break;
5474
5475       case MachinePlaysBlack:
5476       case IcsPlayingWhite:
5477         if (appData.zippyPlay) return FALSE;
5478         if (!white_piece) {
5479             DisplayMoveError(_("You are playing White"));
5480             return FALSE;
5481         }
5482         break;
5483
5484       case EditGame:
5485         if (!white_piece && WhiteOnMove(currentMove)) {
5486             DisplayMoveError(_("It is White's turn"));
5487             return FALSE;
5488         }           
5489         if (white_piece && !WhiteOnMove(currentMove)) {
5490             DisplayMoveError(_("It is Black's turn"));
5491             return FALSE;
5492         }           
5493         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5494             /* Editing correspondence game history */
5495             /* Could disallow this or prompt for confirmation */
5496             cmailOldMove = -1;
5497         }
5498         break;
5499
5500       case BeginningOfGame:
5501         if (appData.icsActive) return FALSE;
5502         if (!appData.noChessProgram) {
5503             if (!white_piece) {
5504                 DisplayMoveError(_("You are playing White"));
5505                 return FALSE;
5506             }
5507         }
5508         break;
5509         
5510       case Training:
5511         if (!white_piece && WhiteOnMove(currentMove)) {
5512             DisplayMoveError(_("It is White's turn"));
5513             return FALSE;
5514         }           
5515         if (white_piece && !WhiteOnMove(currentMove)) {
5516             DisplayMoveError(_("It is Black's turn"));
5517             return FALSE;
5518         }           
5519         break;
5520
5521       default:
5522       case IcsExamining:
5523         break;
5524     }
5525     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5526         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5527         && gameMode != AnalyzeFile && gameMode != Training) {
5528         DisplayMoveError(_("Displayed position is not current"));
5529         return FALSE;
5530     }
5531     return TRUE;
5532 }
5533
5534 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5535 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5536 int lastLoadGameUseList = FALSE;
5537 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5538 ChessMove lastLoadGameStart = (ChessMove) 0;
5539
5540 ChessMove
5541 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5542      int fromX, fromY, toX, toY;
5543      int promoChar;
5544      Boolean captureOwn;
5545 {
5546     ChessMove moveType;
5547     ChessSquare pdown, pup;
5548
5549     /* Check if the user is playing in turn.  This is complicated because we
5550        let the user "pick up" a piece before it is his turn.  So the piece he
5551        tried to pick up may have been captured by the time he puts it down!
5552        Therefore we use the color the user is supposed to be playing in this
5553        test, not the color of the piece that is currently on the starting
5554        square---except in EditGame mode, where the user is playing both
5555        sides; fortunately there the capture race can't happen.  (It can
5556        now happen in IcsExamining mode, but that's just too bad.  The user
5557        will get a somewhat confusing message in that case.)
5558        */
5559
5560     switch (gameMode) {
5561       case PlayFromGameFile:
5562       case AnalyzeFile:
5563       case TwoMachinesPlay:
5564       case EndOfGame:
5565       case IcsObserving:
5566       case IcsIdle:
5567         /* We switched into a game mode where moves are not accepted,
5568            perhaps while the mouse button was down. */
5569         return ImpossibleMove;
5570
5571       case MachinePlaysWhite:
5572         /* User is moving for Black */
5573         if (WhiteOnMove(currentMove)) {
5574             DisplayMoveError(_("It is White's turn"));
5575             return ImpossibleMove;
5576         }
5577         break;
5578
5579       case MachinePlaysBlack:
5580         /* User is moving for White */
5581         if (!WhiteOnMove(currentMove)) {
5582             DisplayMoveError(_("It is Black's turn"));
5583             return ImpossibleMove;
5584         }
5585         break;
5586
5587       case EditGame:
5588       case IcsExamining:
5589       case BeginningOfGame:
5590       case AnalyzeMode:
5591       case Training:
5592         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5593             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5594             /* User is moving for Black */
5595             if (WhiteOnMove(currentMove)) {
5596                 DisplayMoveError(_("It is White's turn"));
5597                 return ImpossibleMove;
5598             }
5599         } else {
5600             /* User is moving for White */
5601             if (!WhiteOnMove(currentMove)) {
5602                 DisplayMoveError(_("It is Black's turn"));
5603                 return ImpossibleMove;
5604             }
5605         }
5606         break;
5607
5608       case IcsPlayingBlack:
5609         /* User is moving for Black */
5610         if (WhiteOnMove(currentMove)) {
5611             if (!appData.premove) {
5612                 DisplayMoveError(_("It is White's turn"));
5613             } else if (toX >= 0 && toY >= 0) {
5614                 premoveToX = toX;
5615                 premoveToY = toY;
5616                 premoveFromX = fromX;
5617                 premoveFromY = fromY;
5618                 premovePromoChar = promoChar;
5619                 gotPremove = 1;
5620                 if (appData.debugMode) 
5621                     fprintf(debugFP, "Got premove: fromX %d,"
5622                             "fromY %d, toX %d, toY %d\n",
5623                             fromX, fromY, toX, toY);
5624             }
5625             return ImpossibleMove;
5626         }
5627         break;
5628
5629       case IcsPlayingWhite:
5630         /* User is moving for White */
5631         if (!WhiteOnMove(currentMove)) {
5632             if (!appData.premove) {
5633                 DisplayMoveError(_("It is Black's turn"));
5634             } else if (toX >= 0 && toY >= 0) {
5635                 premoveToX = toX;
5636                 premoveToY = toY;
5637                 premoveFromX = fromX;
5638                 premoveFromY = fromY;
5639                 premovePromoChar = promoChar;
5640                 gotPremove = 1;
5641                 if (appData.debugMode) 
5642                     fprintf(debugFP, "Got premove: fromX %d,"
5643                             "fromY %d, toX %d, toY %d\n",
5644                             fromX, fromY, toX, toY);
5645             }
5646             return ImpossibleMove;
5647         }
5648         break;
5649
5650       default:
5651         break;
5652
5653       case EditPosition:
5654         /* EditPosition, empty square, or different color piece;
5655            click-click move is possible */
5656         if (toX == -2 || toY == -2) {
5657             boards[0][fromY][fromX] = EmptySquare;
5658             return AmbiguousMove;
5659         } else if (toX >= 0 && toY >= 0) {
5660             boards[0][toY][toX] = boards[0][fromY][fromX];
5661             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5662                 if(boards[0][fromY][0] != EmptySquare) {
5663                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5664                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5665                 }
5666             } else
5667             if(fromX == BOARD_RGHT+1) {
5668                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5669                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5670                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5671                 }
5672             } else
5673             boards[0][fromY][fromX] = EmptySquare;
5674             return AmbiguousMove;
5675         }
5676         return ImpossibleMove;
5677     }
5678
5679     if(toX < 0 || toY < 0) return ImpossibleMove;
5680     pdown = boards[currentMove][fromY][fromX];
5681     pup = boards[currentMove][toY][toX];
5682
5683     /* [HGM] If move started in holdings, it means a drop */
5684     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5685          if( pup != EmptySquare ) return ImpossibleMove;
5686          if(appData.testLegality) {
5687              /* it would be more logical if LegalityTest() also figured out
5688               * which drops are legal. For now we forbid pawns on back rank.
5689               * Shogi is on its own here...
5690               */
5691              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5692                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5693                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5694          }
5695          return WhiteDrop; /* Not needed to specify white or black yet */
5696     }
5697
5698     /* [HGM] always test for legality, to get promotion info */
5699     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5700                                          fromY, fromX, toY, toX, promoChar);
5701     /* [HGM] but possibly ignore an IllegalMove result */
5702     if (appData.testLegality) {
5703         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5704             DisplayMoveError(_("Illegal move"));
5705             return ImpossibleMove;
5706         }
5707     }
5708
5709     return moveType;
5710     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5711        function is made into one that returns an OK move type if FinishMove
5712        should be called. This to give the calling driver routine the
5713        opportunity to finish the userMove input with a promotion popup,
5714        without bothering the user with this for invalid or illegal moves */
5715
5716 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5717 }
5718
5719 /* Common tail of UserMoveEvent and DropMenuEvent */
5720 int
5721 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5722      ChessMove moveType;
5723      int fromX, fromY, toX, toY;
5724      /*char*/int promoChar;
5725 {
5726     char *bookHit = 0;
5727
5728     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5729         // [HGM] superchess: suppress promotions to non-available piece
5730         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5731         if(WhiteOnMove(currentMove)) {
5732             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5733         } else {
5734             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5735         }
5736     }
5737
5738     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5739        move type in caller when we know the move is a legal promotion */
5740     if(moveType == NormalMove && promoChar)
5741         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5742
5743     /* [HGM] convert drag-and-drop piece drops to standard form */
5744     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5745          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5746            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5747                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5748            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5749            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5750            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5751            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5752          fromY = DROP_RANK;
5753     }
5754
5755     /* [HGM] <popupFix> The following if has been moved here from
5756        UserMoveEvent(). Because it seemed to belong here (why not allow
5757        piece drops in training games?), and because it can only be
5758        performed after it is known to what we promote. */
5759     if (gameMode == Training) {
5760       /* compare the move played on the board to the next move in the
5761        * game. If they match, display the move and the opponent's response. 
5762        * If they don't match, display an error message.
5763        */
5764       int saveAnimate;
5765       Board testBoard;
5766       CopyBoard(testBoard, boards[currentMove]);
5767       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5768
5769       if (CompareBoards(testBoard, boards[currentMove+1])) {
5770         ForwardInner(currentMove+1);
5771
5772         /* Autoplay the opponent's response.
5773          * if appData.animate was TRUE when Training mode was entered,
5774          * the response will be animated.
5775          */
5776         saveAnimate = appData.animate;
5777         appData.animate = animateTraining;
5778         ForwardInner(currentMove+1);
5779         appData.animate = saveAnimate;
5780
5781         /* check for the end of the game */
5782         if (currentMove >= forwardMostMove) {
5783           gameMode = PlayFromGameFile;
5784           ModeHighlight();
5785           SetTrainingModeOff();
5786           DisplayInformation(_("End of game"));
5787         }
5788       } else {
5789         DisplayError(_("Incorrect move"), 0);
5790       }
5791       return 1;
5792     }
5793
5794   /* Ok, now we know that the move is good, so we can kill
5795      the previous line in Analysis Mode */
5796   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5797                                 && currentMove < forwardMostMove) {
5798     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5799   }
5800
5801   /* If we need the chess program but it's dead, restart it */
5802   ResurrectChessProgram();
5803
5804   /* A user move restarts a paused game*/
5805   if (pausing)
5806     PauseEvent();
5807
5808   thinkOutput[0] = NULLCHAR;
5809
5810   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5811
5812   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5813
5814   if (gameMode == BeginningOfGame) {
5815     if (appData.noChessProgram) {
5816       gameMode = EditGame;
5817       SetGameInfo();
5818     } else {
5819       char buf[MSG_SIZ];
5820       gameMode = MachinePlaysBlack;
5821       StartClocks();
5822       SetGameInfo();
5823       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5824       DisplayTitle(buf);
5825       if (first.sendName) {
5826         sprintf(buf, "name %s\n", gameInfo.white);
5827         SendToProgram(buf, &first);
5828       }
5829       StartClocks();
5830     }
5831     ModeHighlight();
5832   }
5833
5834   /* Relay move to ICS or chess engine */
5835   if (appData.icsActive) {
5836     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5837         gameMode == IcsExamining) {
5838       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5839         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5840         SendToICS("draw ");
5841         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5842       }
5843       // also send plain move, in case ICS does not understand atomic claims
5844       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5845       ics_user_moved = 1;
5846     }
5847   } else {
5848     if (first.sendTime && (gameMode == BeginningOfGame ||
5849                            gameMode == MachinePlaysWhite ||
5850                            gameMode == MachinePlaysBlack)) {
5851       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5852     }
5853     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5854          // [HGM] book: if program might be playing, let it use book
5855         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5856         first.maybeThinking = TRUE;
5857     } else SendMoveToProgram(forwardMostMove-1, &first);
5858     if (currentMove == cmailOldMove + 1) {
5859       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5860     }
5861   }
5862
5863   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5864
5865   switch (gameMode) {
5866   case EditGame:
5867     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5868     case MT_NONE:
5869     case MT_CHECK:
5870       break;
5871     case MT_CHECKMATE:
5872     case MT_STAINMATE:
5873       if (WhiteOnMove(currentMove)) {
5874         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5875       } else {
5876         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5877       }
5878       break;
5879     case MT_STALEMATE:
5880       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5881       break;
5882     }
5883     break;
5884     
5885   case MachinePlaysBlack:
5886   case MachinePlaysWhite:
5887     /* disable certain menu options while machine is thinking */
5888     SetMachineThinkingEnables();
5889     break;
5890
5891   default:
5892     break;
5893   }
5894
5895   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5896         
5897   if(bookHit) { // [HGM] book: simulate book reply
5898         static char bookMove[MSG_SIZ]; // a bit generous?
5899
5900         programStats.nodes = programStats.depth = programStats.time = 
5901         programStats.score = programStats.got_only_move = 0;
5902         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5903
5904         strcpy(bookMove, "move ");
5905         strcat(bookMove, bookHit);
5906         HandleMachineMove(bookMove, &first);
5907   }
5908   return 1;
5909 }
5910
5911 void
5912 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5913      int fromX, fromY, toX, toY;
5914      int promoChar;
5915 {
5916     /* [HGM] This routine was added to allow calling of its two logical
5917        parts from other modules in the old way. Before, UserMoveEvent()
5918        automatically called FinishMove() if the move was OK, and returned
5919        otherwise. I separated the two, in order to make it possible to
5920        slip a promotion popup in between. But that it always needs two
5921        calls, to the first part, (now called UserMoveTest() ), and to
5922        FinishMove if the first part succeeded. Calls that do not need
5923        to do anything in between, can call this routine the old way. 
5924     */
5925     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5926 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5927     if(moveType == AmbiguousMove)
5928         DrawPosition(FALSE, boards[currentMove]);
5929     else if(moveType != ImpossibleMove && moveType != Comment)
5930         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5931 }
5932
5933 void
5934 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5935      Board board;
5936      int flags;
5937      ChessMove kind;
5938      int rf, ff, rt, ft;
5939      VOIDSTAR closure;
5940 {
5941     typedef char Markers[BOARD_RANKS][BOARD_FILES];
5942     Markers *m = (Markers *) closure;
5943     if(rf == fromY && ff == fromX)
5944         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5945                          || kind == WhiteCapturesEnPassant
5946                          || kind == BlackCapturesEnPassant);
5947     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5948 }
5949
5950 void
5951 MarkTargetSquares(int clear)
5952 {
5953   int x, y;
5954   if(!appData.markers || !appData.highlightDragging || 
5955      !appData.testLegality || gameMode == EditPosition) return;
5956   if(clear) {
5957     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5958   } else {
5959     int capt = 0;
5960     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5961     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5962       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5963       if(capt)
5964       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5965     }
5966   }
5967   DrawPosition(TRUE, NULL);
5968 }
5969
5970 void LeftClick(ClickType clickType, int xPix, int yPix)
5971 {
5972     int x, y;
5973     Boolean saveAnimate;
5974     static int second = 0, promotionChoice = 0;
5975     char promoChoice = NULLCHAR;
5976
5977     if(appData.seekGraph && appData.icsActive && loggedOn &&
5978         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5979         SeekGraphClick(clickType, xPix, yPix, FALSE);
5980         return;
5981     }
5982
5983     if (clickType == Press) ErrorPopDown();
5984     MarkTargetSquares(1);
5985
5986     x = EventToSquare(xPix, BOARD_WIDTH);
5987     y = EventToSquare(yPix, BOARD_HEIGHT);
5988     if (!flipView && y >= 0) {
5989         y = BOARD_HEIGHT - 1 - y;
5990     }
5991     if (flipView && x >= 0) {
5992         x = BOARD_WIDTH - 1 - x;
5993     }
5994
5995     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5996         if(clickType == Release) return; // ignore upclick of click-click destination
5997         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5998         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5999         if(gameInfo.holdingsWidth && 
6000                 (WhiteOnMove(currentMove) 
6001                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6002                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6003             // click in right holdings, for determining promotion piece
6004             ChessSquare p = boards[currentMove][y][x];
6005             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6006             if(p != EmptySquare) {
6007                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6008                 fromX = fromY = -1;
6009                 return;
6010             }
6011         }
6012         DrawPosition(FALSE, boards[currentMove]);
6013         return;
6014     }
6015
6016     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6017     if(clickType == Press
6018             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6019               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6020               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6021         return;
6022
6023     if (fromX == -1) {
6024         if (clickType == Press) {
6025             /* First square */
6026             if (OKToStartUserMove(x, y)) {
6027                 fromX = x;
6028                 fromY = y;
6029                 second = 0;
6030                 MarkTargetSquares(0);
6031                 DragPieceBegin(xPix, yPix);
6032                 if (appData.highlightDragging) {
6033                     SetHighlights(x, y, -1, -1);
6034                 }
6035             }
6036         }
6037         return;
6038     }
6039
6040     /* fromX != -1 */
6041     if (clickType == Press && gameMode != EditPosition) {
6042         ChessSquare fromP;
6043         ChessSquare toP;
6044         int frc;
6045
6046         // ignore off-board to clicks
6047         if(y < 0 || x < 0) return;
6048
6049         /* Check if clicking again on the same color piece */
6050         fromP = boards[currentMove][fromY][fromX];
6051         toP = boards[currentMove][y][x];
6052         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6053         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6054              WhitePawn <= toP && toP <= WhiteKing &&
6055              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6056              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6057             (BlackPawn <= fromP && fromP <= BlackKing && 
6058              BlackPawn <= toP && toP <= BlackKing &&
6059              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6060              !(fromP == BlackKing && toP == BlackRook && frc))) {
6061             /* Clicked again on same color piece -- changed his mind */
6062             second = (x == fromX && y == fromY);
6063             if (appData.highlightDragging) {
6064                 SetHighlights(x, y, -1, -1);
6065             } else {
6066                 ClearHighlights();
6067             }
6068             if (OKToStartUserMove(x, y)) {
6069                 fromX = x;
6070                 fromY = y;
6071                 MarkTargetSquares(0);
6072                 DragPieceBegin(xPix, yPix);
6073             }
6074             return;
6075         }
6076         // ignore clicks on holdings
6077         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6078     }
6079
6080     if (clickType == Release && x == fromX && y == fromY) {
6081         DragPieceEnd(xPix, yPix);
6082         if (appData.animateDragging) {
6083             /* Undo animation damage if any */
6084             DrawPosition(FALSE, NULL);
6085         }
6086         if (second) {
6087             /* Second up/down in same square; just abort move */
6088             second = 0;
6089             fromX = fromY = -1;
6090             ClearHighlights();
6091             gotPremove = 0;
6092             ClearPremoveHighlights();
6093         } else {
6094             /* First upclick in same square; start click-click mode */
6095             SetHighlights(x, y, -1, -1);
6096         }
6097         return;
6098     }
6099
6100     /* we now have a different from- and (possibly off-board) to-square */
6101     /* Completed move */
6102     toX = x;
6103     toY = y;
6104     saveAnimate = appData.animate;
6105     if (clickType == Press) {
6106         /* Finish clickclick move */
6107         if (appData.animate || appData.highlightLastMove) {
6108             SetHighlights(fromX, fromY, toX, toY);
6109         } else {
6110             ClearHighlights();
6111         }
6112     } else {
6113         /* Finish drag move */
6114         if (appData.highlightLastMove) {
6115             SetHighlights(fromX, fromY, toX, toY);
6116         } else {
6117             ClearHighlights();
6118         }
6119         DragPieceEnd(xPix, yPix);
6120         /* Don't animate move and drag both */
6121         appData.animate = FALSE;
6122     }
6123
6124     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6125     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6126         ChessSquare piece = boards[currentMove][fromY][fromX];
6127         if(gameMode == EditPosition && piece != EmptySquare &&
6128            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6129             int n;
6130              
6131             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6132                 n = PieceToNumber(piece - (int)BlackPawn);
6133                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6134                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6135                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6136             } else
6137             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6138                 n = PieceToNumber(piece);
6139                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6140                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6141                 boards[currentMove][n][BOARD_WIDTH-2]++;
6142             }
6143             boards[currentMove][fromY][fromX] = EmptySquare;
6144         }
6145         ClearHighlights();
6146         fromX = fromY = -1;
6147         DrawPosition(TRUE, boards[currentMove]);
6148         return;
6149     }
6150
6151     // off-board moves should not be highlighted
6152     if(x < 0 || x < 0) ClearHighlights();
6153
6154     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6155         SetHighlights(fromX, fromY, toX, toY);
6156         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6157             // [HGM] super: promotion to captured piece selected from holdings
6158             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6159             promotionChoice = TRUE;
6160             // kludge follows to temporarily execute move on display, without promoting yet
6161             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6162             boards[currentMove][toY][toX] = p;
6163             DrawPosition(FALSE, boards[currentMove]);
6164             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6165             boards[currentMove][toY][toX] = q;
6166             DisplayMessage("Click in holdings to choose piece", "");
6167             return;
6168         }
6169         PromotionPopUp();
6170     } else {
6171         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6172         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6173         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6174         fromX = fromY = -1;
6175     }
6176     appData.animate = saveAnimate;
6177     if (appData.animate || appData.animateDragging) {
6178         /* Undo animation damage if needed */
6179         DrawPosition(FALSE, NULL);
6180     }
6181 }
6182
6183 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6184 {   // front-end-free part taken out of PieceMenuPopup
6185     int whichMenu; int xSqr, ySqr;
6186
6187     xSqr = EventToSquare(x, BOARD_WIDTH);
6188     ySqr = EventToSquare(y, BOARD_HEIGHT);
6189     if (action == Release) UnLoadPV(); // [HGM] pv
6190     if (action != Press) return -2;
6191     switch (gameMode) {
6192       case IcsExamining:
6193         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6194       case EditPosition:
6195         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6196         if (xSqr < 0 || ySqr < 0) return -1;\r
6197         whichMenu = 0; // edit-position menu
6198         break;
6199       case IcsObserving:
6200         if(!appData.icsEngineAnalyze) return -1;
6201       case IcsPlayingWhite:
6202       case IcsPlayingBlack:
6203         if(!appData.zippyPlay) goto noZip;
6204       case AnalyzeMode:
6205       case AnalyzeFile:
6206       case MachinePlaysWhite:
6207       case MachinePlaysBlack:
6208       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6209         if (!appData.dropMenu) {
6210           LoadPV(x, y);
6211           return 2; // flag front-end to grab mouse events
6212         }
6213         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6214            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6215       case EditGame:
6216       noZip:
6217         if (xSqr < 0 || ySqr < 0) return -1;
6218         if (!appData.dropMenu || appData.testLegality &&
6219             gameInfo.variant != VariantBughouse &&
6220             gameInfo.variant != VariantCrazyhouse) return -1;
6221         whichMenu = 1; // drop menu
6222         break;
6223       default:
6224         return -1;
6225     }
6226
6227     if (((*fromX = xSqr) < 0) ||
6228         ((*fromY = ySqr) < 0)) {
6229         *fromX = *fromY = -1;
6230         return -1;
6231     }
6232     if (flipView)
6233       *fromX = BOARD_WIDTH - 1 - *fromX;
6234     else
6235       *fromY = BOARD_HEIGHT - 1 - *fromY;
6236
6237     return whichMenu;
6238 }
6239
6240 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6241 {
6242 //    char * hint = lastHint;
6243     FrontEndProgramStats stats;
6244
6245     stats.which = cps == &first ? 0 : 1;
6246     stats.depth = cpstats->depth;
6247     stats.nodes = cpstats->nodes;
6248     stats.score = cpstats->score;
6249     stats.time = cpstats->time;
6250     stats.pv = cpstats->movelist;
6251     stats.hint = lastHint;
6252     stats.an_move_index = 0;
6253     stats.an_move_count = 0;
6254
6255     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6256         stats.hint = cpstats->move_name;
6257         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6258         stats.an_move_count = cpstats->nr_moves;
6259     }
6260
6261     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6262
6263     SetProgramStats( &stats );
6264 }
6265
6266 int
6267 Adjudicate(ChessProgramState *cps)
6268 {       // [HGM] some adjudications useful with buggy engines
6269         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6270         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6271         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6272         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6273         int k, count = 0; static int bare = 1;
6274         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6275         Boolean canAdjudicate = !appData.icsActive;
6276
6277         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6278         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6279             if( appData.testLegality )
6280             {   /* [HGM] Some more adjudications for obstinate engines */
6281                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6282                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6283                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6284                 static int moveCount = 6;
6285                 ChessMove result;
6286                 char *reason = NULL;
6287
6288                 /* Count what is on board. */
6289                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6290                 {   ChessSquare p = boards[forwardMostMove][i][j];
6291                     int m=i;
6292
6293                     switch((int) p)
6294                     {   /* count B,N,R and other of each side */
6295                         case WhiteKing:
6296                         case BlackKing:
6297                              NrK++; break; // [HGM] atomic: count Kings
6298                         case WhiteKnight:
6299                              NrWN++; break;
6300                         case WhiteBishop:
6301                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6302                              bishopsColor |= 1 << ((i^j)&1);
6303                              NrWB++; break;
6304                         case BlackKnight:
6305                              NrBN++; break;
6306                         case BlackBishop:
6307                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6308                              bishopsColor |= 1 << ((i^j)&1);
6309                              NrBB++; break;
6310                         case WhiteRook:
6311                              NrWR++; break;
6312                         case BlackRook:
6313                              NrBR++; break;
6314                         case WhiteQueen:
6315                              NrWQ++; break;
6316                         case BlackQueen:
6317                              NrBQ++; break;
6318                         case EmptySquare: 
6319                              break;
6320                         case BlackPawn:
6321                              m = 7-i;
6322                         case WhitePawn:
6323                              PawnAdvance += m; NrPawns++;
6324                     }
6325                     NrPieces += (p != EmptySquare);
6326                     NrW += ((int)p < (int)BlackPawn);
6327                     if(gameInfo.variant == VariantXiangqi && 
6328                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6329                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6330                         NrW -= ((int)p < (int)BlackPawn);
6331                     }
6332                 }
6333
6334                 /* Some material-based adjudications that have to be made before stalemate test */
6335                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6336                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6337                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6338                      if(canAdjudicate && appData.checkMates) {
6339                          if(engineOpponent)
6340                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6341                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6342                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6343                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6344                          return 1;
6345                      }
6346                 }
6347
6348                 /* Bare King in Shatranj (loses) or Losers (wins) */
6349                 if( NrW == 1 || NrPieces - NrW == 1) {
6350                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6351                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6352                      if(canAdjudicate && appData.checkMates) {
6353                          if(engineOpponent)
6354                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6355                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6356                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6357                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6358                          return 1;
6359                      }
6360                   } else
6361                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6362                   {    /* bare King */
6363                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6364                         if(canAdjudicate && appData.checkMates) {
6365                             /* but only adjudicate if adjudication enabled */
6366                             if(engineOpponent)
6367                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6368                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6370                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6371                             return 1;
6372                         }
6373                   }
6374                 } else bare = 1;
6375
6376
6377             // don't wait for engine to announce game end if we can judge ourselves
6378             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6379               case MT_CHECK:
6380                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6381                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6382                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6383                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6384                             checkCnt++;
6385                         if(checkCnt >= 2) {
6386                             reason = "Xboard adjudication: 3rd check";
6387                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6388                             break;
6389                         }
6390                     }
6391                 }
6392               case MT_NONE:
6393               default:
6394                 break;
6395               case MT_STALEMATE:
6396               case MT_STAINMATE:
6397                 reason = "Xboard adjudication: Stalemate";
6398                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6399                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6400                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6401                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6402                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6403                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6404                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6405                                                                         EP_CHECKMATE : EP_WINS);
6406                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6407                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6408                 }
6409                 break;
6410               case MT_CHECKMATE:
6411                 reason = "Xboard adjudication: Checkmate";
6412                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6413                 break;
6414             }
6415
6416                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6417                     case EP_STALEMATE:
6418                         result = GameIsDrawn; break;
6419                     case EP_CHECKMATE:
6420                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6421                     case EP_WINS:
6422                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6423                     default:
6424                         result = (ChessMove) 0;
6425                 }
6426                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6427                     if(engineOpponent)
6428                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6429                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6430                     GameEnds( result, reason, GE_XBOARD );
6431                     return 1;
6432                 }
6433
6434                 /* Next absolutely insufficient mating material. */
6435                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6436                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6437                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6438                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6439                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6440
6441                      /* always flag draws, for judging claims */
6442                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6443
6444                      if(canAdjudicate && appData.materialDraws) {
6445                          /* but only adjudicate them if adjudication enabled */
6446                          if(engineOpponent) {
6447                            SendToProgram("force\n", engineOpponent); // suppress reply
6448                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6449                          }
6450                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6451                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6452                          return 1;
6453                      }
6454                 }
6455
6456                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6457                 if(NrPieces == 4 && 
6458                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6459                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6460                    || NrWN==2 || NrBN==2     /* KNNK */
6461                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6462                   ) ) {
6463                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6464                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6465                           if(engineOpponent) {
6466                             SendToProgram("force\n", engineOpponent); // suppress reply
6467                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6468                           }
6469                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6470                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6471                           return 1;
6472                      }
6473                 } else moveCount = 6;
6474             }
6475         }
6476           
6477         if (appData.debugMode) { int i;
6478             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6479                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6480                     appData.drawRepeats);
6481             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6482               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6483             
6484         }
6485
6486         // Repetition draws and 50-move rule can be applied independently of legality testing
6487
6488                 /* Check for rep-draws */
6489                 count = 0;
6490                 for(k = forwardMostMove-2;
6491                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6492                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6493                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6494                     k-=2)
6495                 {   int rights=0;
6496                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6497                         /* compare castling rights */
6498                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6499                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6500                                 rights++; /* King lost rights, while rook still had them */
6501                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6502                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6503                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6504                                    rights++; /* but at least one rook lost them */
6505                         }
6506                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6507                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6508                                 rights++; 
6509                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6510                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6511                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6512                                    rights++;
6513                         }
6514                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6515                             && appData.drawRepeats > 1) {
6516                              /* adjudicate after user-specified nr of repeats */
6517                              if(engineOpponent) {
6518                                SendToProgram("force\n", engineOpponent); // suppress reply
6519                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6520                              }
6521                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6522                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6523                                 // [HGM] xiangqi: check for forbidden perpetuals
6524                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6525                                 for(m=forwardMostMove; m>k; m-=2) {
6526                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6527                                         ourPerpetual = 0; // the current mover did not always check
6528                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6529                                         hisPerpetual = 0; // the opponent did not always check
6530                                 }
6531                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6532                                                                         ourPerpetual, hisPerpetual);
6533                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6534                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6535                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6536                                     return 1;
6537                                 }
6538                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6539                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6540                                 // Now check for perpetual chases
6541                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6542                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6543                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6544                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6545                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6546                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6547                                         return 1;
6548                                     }
6549                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6550                                         break; // Abort repetition-checking loop.
6551                                 }
6552                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6553                              }
6554                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6555                              return 1;
6556                         }
6557                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6558                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6559                     }
6560                 }
6561
6562                 /* Now we test for 50-move draws. Determine ply count */
6563                 count = forwardMostMove;
6564                 /* look for last irreversble move */
6565                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6566                     count--;
6567                 /* if we hit starting position, add initial plies */
6568                 if( count == backwardMostMove )
6569                     count -= initialRulePlies;
6570                 count = forwardMostMove - count; 
6571                 if( count >= 100)
6572                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6573                          /* this is used to judge if draw claims are legal */
6574                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6575                          if(engineOpponent) {
6576                            SendToProgram("force\n", engineOpponent); // suppress reply
6577                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6578                          }
6579                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6580                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6581                          return 1;
6582                 }
6583
6584                 /* if draw offer is pending, treat it as a draw claim
6585                  * when draw condition present, to allow engines a way to
6586                  * claim draws before making their move to avoid a race
6587                  * condition occurring after their move
6588                  */
6589                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6590                          char *p = NULL;
6591                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6592                              p = "Draw claim: 50-move rule";
6593                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6594                              p = "Draw claim: 3-fold repetition";
6595                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6596                              p = "Draw claim: insufficient mating material";
6597                          if( p != NULL && canAdjudicate) {
6598                              if(engineOpponent) {
6599                                SendToProgram("force\n", engineOpponent); // suppress reply
6600                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6601                              }
6602                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6604                              return 1;
6605                          }
6606                 }
6607
6608                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6609                     if(engineOpponent) {
6610                       SendToProgram("force\n", engineOpponent); // suppress reply
6611                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6612                     }
6613                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6614                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6615                     return 1;
6616                 }
6617         return 0;
6618 }
6619
6620 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6621 {   // [HGM] book: this routine intercepts moves to simulate book replies
6622     char *bookHit = NULL;
6623
6624     //first determine if the incoming move brings opponent into his book
6625     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6626         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6627     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6628     if(bookHit != NULL && !cps->bookSuspend) {
6629         // make sure opponent is not going to reply after receiving move to book position
6630         SendToProgram("force\n", cps);
6631         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6632     }
6633     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6634     // now arrange restart after book miss
6635     if(bookHit) {
6636         // after a book hit we never send 'go', and the code after the call to this routine
6637         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6638         char buf[MSG_SIZ];
6639         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6640         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6641         SendToProgram(buf, cps);
6642         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6643     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6644         SendToProgram("go\n", cps);
6645         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6646     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6647         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6648             SendToProgram("go\n", cps); 
6649         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6650     }
6651     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6652 }
6653
6654 char *savedMessage;
6655 ChessProgramState *savedState;
6656 void DeferredBookMove(void)
6657 {
6658         if(savedState->lastPing != savedState->lastPong)
6659                     ScheduleDelayedEvent(DeferredBookMove, 10);
6660         else
6661         HandleMachineMove(savedMessage, savedState);
6662 }
6663
6664 void
6665 HandleMachineMove(message, cps)
6666      char *message;
6667      ChessProgramState *cps;
6668 {
6669     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6670     char realname[MSG_SIZ];
6671     int fromX, fromY, toX, toY;
6672     ChessMove moveType;
6673     char promoChar;
6674     char *p;
6675     int machineWhite;
6676     char *bookHit;
6677
6678     cps->userError = 0;
6679
6680 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6681     /*
6682      * Kludge to ignore BEL characters
6683      */
6684     while (*message == '\007') message++;
6685
6686     /*
6687      * [HGM] engine debug message: ignore lines starting with '#' character
6688      */
6689     if(cps->debug && *message == '#') return;
6690
6691     /*
6692      * Look for book output
6693      */
6694     if (cps == &first && bookRequested) {
6695         if (message[0] == '\t' || message[0] == ' ') {
6696             /* Part of the book output is here; append it */
6697             strcat(bookOutput, message);
6698             strcat(bookOutput, "  \n");
6699             return;
6700         } else if (bookOutput[0] != NULLCHAR) {
6701             /* All of book output has arrived; display it */
6702             char *p = bookOutput;
6703             while (*p != NULLCHAR) {
6704                 if (*p == '\t') *p = ' ';
6705                 p++;
6706             }
6707             DisplayInformation(bookOutput);
6708             bookRequested = FALSE;
6709             /* Fall through to parse the current output */
6710         }
6711     }
6712
6713     /*
6714      * Look for machine move.
6715      */
6716     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6717         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6718     {
6719         /* This method is only useful on engines that support ping */
6720         if (cps->lastPing != cps->lastPong) {
6721           if (gameMode == BeginningOfGame) {
6722             /* Extra move from before last new; ignore */
6723             if (appData.debugMode) {
6724                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6725             }
6726           } else {
6727             if (appData.debugMode) {
6728                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6729                         cps->which, gameMode);
6730             }
6731
6732             SendToProgram("undo\n", cps);
6733           }
6734           return;
6735         }
6736
6737         switch (gameMode) {
6738           case BeginningOfGame:
6739             /* Extra move from before last reset; ignore */
6740             if (appData.debugMode) {
6741                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6742             }
6743             return;
6744
6745           case EndOfGame:
6746           case IcsIdle:
6747           default:
6748             /* Extra move after we tried to stop.  The mode test is
6749                not a reliable way of detecting this problem, but it's
6750                the best we can do on engines that don't support ping.
6751             */
6752             if (appData.debugMode) {
6753                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6754                         cps->which, gameMode);
6755             }
6756             SendToProgram("undo\n", cps);
6757             return;
6758
6759           case MachinePlaysWhite:
6760           case IcsPlayingWhite:
6761             machineWhite = TRUE;
6762             break;
6763
6764           case MachinePlaysBlack:
6765           case IcsPlayingBlack:
6766             machineWhite = FALSE;
6767             break;
6768
6769           case TwoMachinesPlay:
6770             machineWhite = (cps->twoMachinesColor[0] == 'w');
6771             break;
6772         }
6773         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6774             if (appData.debugMode) {
6775                 fprintf(debugFP,
6776                         "Ignoring move out of turn by %s, gameMode %d"
6777                         ", forwardMost %d\n",
6778                         cps->which, gameMode, forwardMostMove);
6779             }
6780             return;
6781         }
6782
6783     if (appData.debugMode) { int f = forwardMostMove;
6784         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6785                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6786                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6787     }
6788         if(cps->alphaRank) AlphaRank(machineMove, 4);
6789         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6790                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6791             /* Machine move could not be parsed; ignore it. */
6792             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6793                     machineMove, cps->which);
6794             DisplayError(buf1, 0);
6795             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6796                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6797             if (gameMode == TwoMachinesPlay) {
6798               GameEnds(machineWhite ? BlackWins : WhiteWins,
6799                        buf1, GE_XBOARD);
6800             }
6801             return;
6802         }
6803
6804         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6805         /* So we have to redo legality test with true e.p. status here,  */
6806         /* to make sure an illegal e.p. capture does not slip through,   */
6807         /* to cause a forfeit on a justified illegal-move complaint      */
6808         /* of the opponent.                                              */
6809         if( gameMode==TwoMachinesPlay && appData.testLegality
6810             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6811                                                               ) {
6812            ChessMove moveType;
6813            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6814                              fromY, fromX, toY, toX, promoChar);
6815             if (appData.debugMode) {
6816                 int i;
6817                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6818                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6819                 fprintf(debugFP, "castling rights\n");
6820             }
6821             if(moveType == IllegalMove) {
6822                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6823                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6824                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6825                            buf1, GE_XBOARD);
6826                 return;
6827            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6828            /* [HGM] Kludge to handle engines that send FRC-style castling
6829               when they shouldn't (like TSCP-Gothic) */
6830            switch(moveType) {
6831              case WhiteASideCastleFR:
6832              case BlackASideCastleFR:
6833                toX+=2;
6834                currentMoveString[2]++;
6835                break;
6836              case WhiteHSideCastleFR:
6837              case BlackHSideCastleFR:
6838                toX--;
6839                currentMoveString[2]--;
6840                break;
6841              default: ; // nothing to do, but suppresses warning of pedantic compilers
6842            }
6843         }
6844         hintRequested = FALSE;
6845         lastHint[0] = NULLCHAR;
6846         bookRequested = FALSE;
6847         /* Program may be pondering now */
6848         cps->maybeThinking = TRUE;
6849         if (cps->sendTime == 2) cps->sendTime = 1;
6850         if (cps->offeredDraw) cps->offeredDraw--;
6851
6852         /* currentMoveString is set as a side-effect of ParseOneMove */
6853         strcpy(machineMove, currentMoveString);
6854         strcat(machineMove, "\n");
6855         strcpy(moveList[forwardMostMove], machineMove);
6856
6857         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6858
6859         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6860         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6861             int count = 0;
6862
6863             while( count < adjudicateLossPlies ) {
6864                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6865
6866                 if( count & 1 ) {
6867                     score = -score; /* Flip score for winning side */
6868                 }
6869
6870                 if( score > adjudicateLossThreshold ) {
6871                     break;
6872                 }
6873
6874                 count++;
6875             }
6876
6877             if( count >= adjudicateLossPlies ) {
6878                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6879
6880                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6881                     "Xboard adjudication", 
6882                     GE_XBOARD );
6883
6884                 return;
6885             }
6886         }
6887
6888         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6889
6890 #if ZIPPY
6891         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6892             first.initDone) {
6893           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6894                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6895                 SendToICS("draw ");
6896                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6897           }
6898           SendMoveToICS(moveType, fromX, fromY, toX, toY);
6899           ics_user_moved = 1;
6900           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6901                 char buf[3*MSG_SIZ];
6902
6903                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6904                         programStats.score / 100.,
6905                         programStats.depth,
6906                         programStats.time / 100.,
6907                         (unsigned int)programStats.nodes,
6908                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6909                         programStats.movelist);
6910                 SendToICS(buf);
6911 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6912           }
6913         }
6914 #endif
6915
6916         /* [AS] Save move info and clear stats for next move */
6917         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6918         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6919         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
6920         ClearProgramStats();
6921         thinkOutput[0] = NULLCHAR;
6922         hiddenThinkOutputState = 0;
6923
6924         bookHit = NULL;
6925         if (gameMode == TwoMachinesPlay) {
6926             /* [HGM] relaying draw offers moved to after reception of move */
6927             /* and interpreting offer as claim if it brings draw condition */
6928             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6929                 SendToProgram("draw\n", cps->other);
6930             }
6931             if (cps->other->sendTime) {
6932                 SendTimeRemaining(cps->other,
6933                                   cps->other->twoMachinesColor[0] == 'w');
6934             }
6935             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6936             if (firstMove && !bookHit) {
6937                 firstMove = FALSE;
6938                 if (cps->other->useColors) {
6939                   SendToProgram(cps->other->twoMachinesColor, cps->other);
6940                 }
6941                 SendToProgram("go\n", cps->other);
6942             }
6943             cps->other->maybeThinking = TRUE;
6944         }
6945
6946         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6947         
6948         if (!pausing && appData.ringBellAfterMoves) {
6949             RingBell();
6950         }
6951
6952         /* 
6953          * Reenable menu items that were disabled while
6954          * machine was thinking
6955          */
6956         if (gameMode != TwoMachinesPlay)
6957             SetUserThinkingEnables();
6958
6959         // [HGM] book: after book hit opponent has received move and is now in force mode
6960         // force the book reply into it, and then fake that it outputted this move by jumping
6961         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6962         if(bookHit) {
6963                 static char bookMove[MSG_SIZ]; // a bit generous?
6964
6965                 strcpy(bookMove, "move ");
6966                 strcat(bookMove, bookHit);
6967                 message = bookMove;
6968                 cps = cps->other;
6969                 programStats.nodes = programStats.depth = programStats.time = 
6970                 programStats.score = programStats.got_only_move = 0;
6971                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6972
6973                 if(cps->lastPing != cps->lastPong) {
6974                     savedMessage = message; // args for deferred call
6975                     savedState = cps;
6976                     ScheduleDelayedEvent(DeferredBookMove, 10);
6977                     return;
6978                 }
6979                 goto FakeBookMove;
6980         }
6981
6982         return;
6983     }
6984
6985     /* Set special modes for chess engines.  Later something general
6986      *  could be added here; for now there is just one kludge feature,
6987      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
6988      *  when "xboard" is given as an interactive command.
6989      */
6990     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6991         cps->useSigint = FALSE;
6992         cps->useSigterm = FALSE;
6993     }
6994     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6995       ParseFeatures(message+8, cps);
6996       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6997     }
6998
6999     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7000      * want this, I was asked to put it in, and obliged.
7001      */
7002     if (!strncmp(message, "setboard ", 9)) {
7003         Board initial_position;
7004
7005         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7006
7007         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7008             DisplayError(_("Bad FEN received from engine"), 0);
7009             return ;
7010         } else {
7011            Reset(TRUE, FALSE);
7012            CopyBoard(boards[0], initial_position);
7013            initialRulePlies = FENrulePlies;
7014            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7015            else gameMode = MachinePlaysBlack;                 
7016            DrawPosition(FALSE, boards[currentMove]);
7017         }
7018         return;
7019     }
7020
7021     /*
7022      * Look for communication commands
7023      */
7024     if (!strncmp(message, "telluser ", 9)) {
7025         DisplayNote(message + 9);
7026         return;
7027     }
7028     if (!strncmp(message, "tellusererror ", 14)) {
7029         cps->userError = 1;
7030         DisplayError(message + 14, 0);
7031         return;
7032     }
7033     if (!strncmp(message, "tellopponent ", 13)) {
7034       if (appData.icsActive) {
7035         if (loggedOn) {
7036           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7037           SendToICS(buf1);
7038         }
7039       } else {
7040         DisplayNote(message + 13);
7041       }
7042       return;
7043     }
7044     if (!strncmp(message, "tellothers ", 11)) {
7045       if (appData.icsActive) {
7046         if (loggedOn) {
7047           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7048           SendToICS(buf1);
7049         }
7050       }
7051       return;
7052     }
7053     if (!strncmp(message, "tellall ", 8)) {
7054       if (appData.icsActive) {
7055         if (loggedOn) {
7056           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7057           SendToICS(buf1);
7058         }
7059       } else {
7060         DisplayNote(message + 8);
7061       }
7062       return;
7063     }
7064     if (strncmp(message, "warning", 7) == 0) {
7065         /* Undocumented feature, use tellusererror in new code */
7066         DisplayError(message, 0);
7067         return;
7068     }
7069     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7070         strcpy(realname, cps->tidy);
7071         strcat(realname, " query");
7072         AskQuestion(realname, buf2, buf1, cps->pr);
7073         return;
7074     }
7075     /* Commands from the engine directly to ICS.  We don't allow these to be 
7076      *  sent until we are logged on. Crafty kibitzes have been known to 
7077      *  interfere with the login process.
7078      */
7079     if (loggedOn) {
7080         if (!strncmp(message, "tellics ", 8)) {
7081             SendToICS(message + 8);
7082             SendToICS("\n");
7083             return;
7084         }
7085         if (!strncmp(message, "tellicsnoalias ", 15)) {
7086             SendToICS(ics_prefix);
7087             SendToICS(message + 15);
7088             SendToICS("\n");
7089             return;
7090         }
7091         /* The following are for backward compatibility only */
7092         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7093             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7094             SendToICS(ics_prefix);
7095             SendToICS(message);
7096             SendToICS("\n");
7097             return;
7098         }
7099     }
7100     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7101         return;
7102     }
7103     /*
7104      * If the move is illegal, cancel it and redraw the board.
7105      * Also deal with other error cases.  Matching is rather loose
7106      * here to accommodate engines written before the spec.
7107      */
7108     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7109         strncmp(message, "Error", 5) == 0) {
7110         if (StrStr(message, "name") || 
7111             StrStr(message, "rating") || StrStr(message, "?") ||
7112             StrStr(message, "result") || StrStr(message, "board") ||
7113             StrStr(message, "bk") || StrStr(message, "computer") ||
7114             StrStr(message, "variant") || StrStr(message, "hint") ||
7115             StrStr(message, "random") || StrStr(message, "depth") ||
7116             StrStr(message, "accepted")) {
7117             return;
7118         }
7119         if (StrStr(message, "protover")) {
7120           /* Program is responding to input, so it's apparently done
7121              initializing, and this error message indicates it is
7122              protocol version 1.  So we don't need to wait any longer
7123              for it to initialize and send feature commands. */
7124           FeatureDone(cps, 1);
7125           cps->protocolVersion = 1;
7126           return;
7127         }
7128         cps->maybeThinking = FALSE;
7129
7130         if (StrStr(message, "draw")) {
7131             /* Program doesn't have "draw" command */
7132             cps->sendDrawOffers = 0;
7133             return;
7134         }
7135         if (cps->sendTime != 1 &&
7136             (StrStr(message, "time") || StrStr(message, "otim"))) {
7137           /* Program apparently doesn't have "time" or "otim" command */
7138           cps->sendTime = 0;
7139           return;
7140         }
7141         if (StrStr(message, "analyze")) {
7142             cps->analysisSupport = FALSE;
7143             cps->analyzing = FALSE;
7144             Reset(FALSE, TRUE);
7145             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7146             DisplayError(buf2, 0);
7147             return;
7148         }
7149         if (StrStr(message, "(no matching move)st")) {
7150           /* Special kludge for GNU Chess 4 only */
7151           cps->stKludge = TRUE;
7152           SendTimeControl(cps, movesPerSession, timeControl,
7153                           timeIncrement, appData.searchDepth,
7154                           searchTime);
7155           return;
7156         }
7157         if (StrStr(message, "(no matching move)sd")) {
7158           /* Special kludge for GNU Chess 4 only */
7159           cps->sdKludge = TRUE;
7160           SendTimeControl(cps, movesPerSession, timeControl,
7161                           timeIncrement, appData.searchDepth,
7162                           searchTime);
7163           return;
7164         }
7165         if (!StrStr(message, "llegal")) {
7166             return;
7167         }
7168         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7169             gameMode == IcsIdle) return;
7170         if (forwardMostMove <= backwardMostMove) return;
7171         if (pausing) PauseEvent();
7172       if(appData.forceIllegal) {
7173             // [HGM] illegal: machine refused move; force position after move into it
7174           SendToProgram("force\n", cps);
7175           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7176                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7177                 // when black is to move, while there might be nothing on a2 or black
7178                 // might already have the move. So send the board as if white has the move.
7179                 // But first we must change the stm of the engine, as it refused the last move
7180                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7181                 if(WhiteOnMove(forwardMostMove)) {
7182                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7183                     SendBoard(cps, forwardMostMove); // kludgeless board
7184                 } else {
7185                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7186                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7187                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7188                 }
7189           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7190             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7191                  gameMode == TwoMachinesPlay)
7192               SendToProgram("go\n", cps);
7193             return;
7194       } else
7195         if (gameMode == PlayFromGameFile) {
7196             /* Stop reading this game file */
7197             gameMode = EditGame;
7198             ModeHighlight();
7199         }
7200         currentMove = --forwardMostMove;
7201         DisplayMove(currentMove-1); /* before DisplayMoveError */
7202         SwitchClocks();
7203         DisplayBothClocks();
7204         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7205                 parseList[currentMove], cps->which);
7206         DisplayMoveError(buf1);
7207         DrawPosition(FALSE, boards[currentMove]);
7208
7209         /* [HGM] illegal-move claim should forfeit game when Xboard */
7210         /* only passes fully legal moves                            */
7211         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7212             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7213                                 "False illegal-move claim", GE_XBOARD );
7214         }
7215         return;
7216     }
7217     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7218         /* Program has a broken "time" command that
7219            outputs a string not ending in newline.
7220            Don't use it. */
7221         cps->sendTime = 0;
7222     }
7223     
7224     /*
7225      * If chess program startup fails, exit with an error message.
7226      * Attempts to recover here are futile.
7227      */
7228     if ((StrStr(message, "unknown host") != NULL)
7229         || (StrStr(message, "No remote directory") != NULL)
7230         || (StrStr(message, "not found") != NULL)
7231         || (StrStr(message, "No such file") != NULL)
7232         || (StrStr(message, "can't alloc") != NULL)
7233         || (StrStr(message, "Permission denied") != NULL)) {
7234
7235         cps->maybeThinking = FALSE;
7236         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7237                 cps->which, cps->program, cps->host, message);
7238         RemoveInputSource(cps->isr);
7239         DisplayFatalError(buf1, 0, 1);
7240         return;
7241     }
7242     
7243     /* 
7244      * Look for hint output
7245      */
7246     if (sscanf(message, "Hint: %s", buf1) == 1) {
7247         if (cps == &first && hintRequested) {
7248             hintRequested = FALSE;
7249             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7250                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7251                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7252                                     PosFlags(forwardMostMove),
7253                                     fromY, fromX, toY, toX, promoChar, buf1);
7254                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7255                 DisplayInformation(buf2);
7256             } else {
7257                 /* Hint move could not be parsed!? */
7258               snprintf(buf2, sizeof(buf2),
7259                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7260                         buf1, cps->which);
7261                 DisplayError(buf2, 0);
7262             }
7263         } else {
7264             strcpy(lastHint, buf1);
7265         }
7266         return;
7267     }
7268
7269     /*
7270      * Ignore other messages if game is not in progress
7271      */
7272     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7273         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7274
7275     /*
7276      * look for win, lose, draw, or draw offer
7277      */
7278     if (strncmp(message, "1-0", 3) == 0) {
7279         char *p, *q, *r = "";
7280         p = strchr(message, '{');
7281         if (p) {
7282             q = strchr(p, '}');
7283             if (q) {
7284                 *q = NULLCHAR;
7285                 r = p + 1;
7286             }
7287         }
7288         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7289         return;
7290     } else if (strncmp(message, "0-1", 3) == 0) {
7291         char *p, *q, *r = "";
7292         p = strchr(message, '{');
7293         if (p) {
7294             q = strchr(p, '}');
7295             if (q) {
7296                 *q = NULLCHAR;
7297                 r = p + 1;
7298             }
7299         }
7300         /* Kludge for Arasan 4.1 bug */
7301         if (strcmp(r, "Black resigns") == 0) {
7302             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7303             return;
7304         }
7305         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7306         return;
7307     } else if (strncmp(message, "1/2", 3) == 0) {
7308         char *p, *q, *r = "";
7309         p = strchr(message, '{');
7310         if (p) {
7311             q = strchr(p, '}');
7312             if (q) {
7313                 *q = NULLCHAR;
7314                 r = p + 1;
7315             }
7316         }
7317             
7318         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7319         return;
7320
7321     } else if (strncmp(message, "White resign", 12) == 0) {
7322         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7323         return;
7324     } else if (strncmp(message, "Black resign", 12) == 0) {
7325         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7326         return;
7327     } else if (strncmp(message, "White matches", 13) == 0 ||
7328                strncmp(message, "Black matches", 13) == 0   ) {
7329         /* [HGM] ignore GNUShogi noises */
7330         return;
7331     } else if (strncmp(message, "White", 5) == 0 &&
7332                message[5] != '(' &&
7333                StrStr(message, "Black") == NULL) {
7334         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7335         return;
7336     } else if (strncmp(message, "Black", 5) == 0 &&
7337                message[5] != '(') {
7338         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7339         return;
7340     } else if (strcmp(message, "resign") == 0 ||
7341                strcmp(message, "computer resigns") == 0) {
7342         switch (gameMode) {
7343           case MachinePlaysBlack:
7344           case IcsPlayingBlack:
7345             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7346             break;
7347           case MachinePlaysWhite:
7348           case IcsPlayingWhite:
7349             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7350             break;
7351           case TwoMachinesPlay:
7352             if (cps->twoMachinesColor[0] == 'w')
7353               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7354             else
7355               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7356             break;
7357           default:
7358             /* can't happen */
7359             break;
7360         }
7361         return;
7362     } else if (strncmp(message, "opponent mates", 14) == 0) {
7363         switch (gameMode) {
7364           case MachinePlaysBlack:
7365           case IcsPlayingBlack:
7366             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7367             break;
7368           case MachinePlaysWhite:
7369           case IcsPlayingWhite:
7370             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7371             break;
7372           case TwoMachinesPlay:
7373             if (cps->twoMachinesColor[0] == 'w')
7374               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7375             else
7376               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7377             break;
7378           default:
7379             /* can't happen */
7380             break;
7381         }
7382         return;
7383     } else if (strncmp(message, "computer mates", 14) == 0) {
7384         switch (gameMode) {
7385           case MachinePlaysBlack:
7386           case IcsPlayingBlack:
7387             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7388             break;
7389           case MachinePlaysWhite:
7390           case IcsPlayingWhite:
7391             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7392             break;
7393           case TwoMachinesPlay:
7394             if (cps->twoMachinesColor[0] == 'w')
7395               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7396             else
7397               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7398             break;
7399           default:
7400             /* can't happen */
7401             break;
7402         }
7403         return;
7404     } else if (strncmp(message, "checkmate", 9) == 0) {
7405         if (WhiteOnMove(forwardMostMove)) {
7406             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7407         } else {
7408             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7409         }
7410         return;
7411     } else if (strstr(message, "Draw") != NULL ||
7412                strstr(message, "game is a draw") != NULL) {
7413         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7414         return;
7415     } else if (strstr(message, "offer") != NULL &&
7416                strstr(message, "draw") != NULL) {
7417 #if ZIPPY
7418         if (appData.zippyPlay && first.initDone) {
7419             /* Relay offer to ICS */
7420             SendToICS(ics_prefix);
7421             SendToICS("draw\n");
7422         }
7423 #endif
7424         cps->offeredDraw = 2; /* valid until this engine moves twice */
7425         if (gameMode == TwoMachinesPlay) {
7426             if (cps->other->offeredDraw) {
7427                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7428             /* [HGM] in two-machine mode we delay relaying draw offer      */
7429             /* until after we also have move, to see if it is really claim */
7430             }
7431         } else if (gameMode == MachinePlaysWhite ||
7432                    gameMode == MachinePlaysBlack) {
7433           if (userOfferedDraw) {
7434             DisplayInformation(_("Machine accepts your draw offer"));
7435             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7436           } else {
7437             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7438           }
7439         }
7440     }
7441
7442     
7443     /*
7444      * Look for thinking output
7445      */
7446     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7447           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7448                                 ) {
7449         int plylev, mvleft, mvtot, curscore, time;
7450         char mvname[MOVE_LEN];
7451         u64 nodes; // [DM]
7452         char plyext;
7453         int ignore = FALSE;
7454         int prefixHint = FALSE;
7455         mvname[0] = NULLCHAR;
7456
7457         switch (gameMode) {
7458           case MachinePlaysBlack:
7459           case IcsPlayingBlack:
7460             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7461             break;
7462           case MachinePlaysWhite:
7463           case IcsPlayingWhite:
7464             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7465             break;
7466           case AnalyzeMode:
7467           case AnalyzeFile:
7468             break;
7469           case IcsObserving: /* [DM] icsEngineAnalyze */
7470             if (!appData.icsEngineAnalyze) ignore = TRUE;
7471             break;
7472           case TwoMachinesPlay:
7473             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7474                 ignore = TRUE;
7475             }
7476             break;
7477           default:
7478             ignore = TRUE;
7479             break;
7480         }
7481
7482         if (!ignore) {
7483             buf1[0] = NULLCHAR;
7484             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7485                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7486
7487                 if (plyext != ' ' && plyext != '\t') {
7488                     time *= 100;
7489                 }
7490
7491                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7492                 if( cps->scoreIsAbsolute && 
7493                     ( gameMode == MachinePlaysBlack ||
7494                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7495                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7496                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7497                      !WhiteOnMove(currentMove)
7498                     ) )
7499                 {
7500                     curscore = -curscore;
7501                 }
7502
7503
7504                 programStats.depth = plylev;
7505                 programStats.nodes = nodes;
7506                 programStats.time = time;
7507                 programStats.score = curscore;
7508                 programStats.got_only_move = 0;
7509
7510                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7511                         int ticklen;
7512
7513                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7514                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7515                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7516                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7517                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7518                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7519                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7520                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7521                 }
7522
7523                 /* Buffer overflow protection */
7524                 if (buf1[0] != NULLCHAR) {
7525                     if (strlen(buf1) >= sizeof(programStats.movelist)
7526                         && appData.debugMode) {
7527                         fprintf(debugFP,
7528                                 "PV is too long; using the first %u bytes.\n",
7529                                 (unsigned) sizeof(programStats.movelist) - 1);
7530                     }
7531
7532                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7533                 } else {
7534                     sprintf(programStats.movelist, " no PV\n");
7535                 }
7536
7537                 if (programStats.seen_stat) {
7538                     programStats.ok_to_send = 1;
7539                 }
7540
7541                 if (strchr(programStats.movelist, '(') != NULL) {
7542                     programStats.line_is_book = 1;
7543                     programStats.nr_moves = 0;
7544                     programStats.moves_left = 0;
7545                 } else {
7546                     programStats.line_is_book = 0;
7547                 }
7548
7549                 SendProgramStatsToFrontend( cps, &programStats );
7550
7551                 /* 
7552                     [AS] Protect the thinkOutput buffer from overflow... this
7553                     is only useful if buf1 hasn't overflowed first!
7554                 */
7555                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7556                         plylev, 
7557                         (gameMode == TwoMachinesPlay ?
7558                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7559                         ((double) curscore) / 100.0,
7560                         prefixHint ? lastHint : "",
7561                         prefixHint ? " " : "" );
7562
7563                 if( buf1[0] != NULLCHAR ) {
7564                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7565
7566                     if( strlen(buf1) > max_len ) {
7567                         if( appData.debugMode) {
7568                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7569                         }
7570                         buf1[max_len+1] = '\0';
7571                     }
7572
7573                     strcat( thinkOutput, buf1 );
7574                 }
7575
7576                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7577                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7578                     DisplayMove(currentMove - 1);
7579                 }
7580                 return;
7581
7582             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7583                 /* crafty (9.25+) says "(only move) <move>"
7584                  * if there is only 1 legal move
7585                  */
7586                 sscanf(p, "(only move) %s", buf1);
7587                 sprintf(thinkOutput, "%s (only move)", buf1);
7588                 sprintf(programStats.movelist, "%s (only move)", buf1);
7589                 programStats.depth = 1;
7590                 programStats.nr_moves = 1;
7591                 programStats.moves_left = 1;
7592                 programStats.nodes = 1;
7593                 programStats.time = 1;
7594                 programStats.got_only_move = 1;
7595
7596                 /* Not really, but we also use this member to
7597                    mean "line isn't going to change" (Crafty
7598                    isn't searching, so stats won't change) */
7599                 programStats.line_is_book = 1;
7600
7601                 SendProgramStatsToFrontend( cps, &programStats );
7602                 
7603                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7604                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7605                     DisplayMove(currentMove - 1);
7606                 }
7607                 return;
7608             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7609                               &time, &nodes, &plylev, &mvleft,
7610                               &mvtot, mvname) >= 5) {
7611                 /* The stat01: line is from Crafty (9.29+) in response
7612                    to the "." command */
7613                 programStats.seen_stat = 1;
7614                 cps->maybeThinking = TRUE;
7615
7616                 if (programStats.got_only_move || !appData.periodicUpdates)
7617                   return;
7618
7619                 programStats.depth = plylev;
7620                 programStats.time = time;
7621                 programStats.nodes = nodes;
7622                 programStats.moves_left = mvleft;
7623                 programStats.nr_moves = mvtot;
7624                 strcpy(programStats.move_name, mvname);
7625                 programStats.ok_to_send = 1;
7626                 programStats.movelist[0] = '\0';
7627
7628                 SendProgramStatsToFrontend( cps, &programStats );
7629
7630                 return;
7631
7632             } else if (strncmp(message,"++",2) == 0) {
7633                 /* Crafty 9.29+ outputs this */
7634                 programStats.got_fail = 2;
7635                 return;
7636
7637             } else if (strncmp(message,"--",2) == 0) {
7638                 /* Crafty 9.29+ outputs this */
7639                 programStats.got_fail = 1;
7640                 return;
7641
7642             } else if (thinkOutput[0] != NULLCHAR &&
7643                        strncmp(message, "    ", 4) == 0) {
7644                 unsigned message_len;
7645
7646                 p = message;
7647                 while (*p && *p == ' ') p++;
7648
7649                 message_len = strlen( p );
7650
7651                 /* [AS] Avoid buffer overflow */
7652                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7653                     strcat(thinkOutput, " ");
7654                     strcat(thinkOutput, p);
7655                 }
7656
7657                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7658                     strcat(programStats.movelist, " ");
7659                     strcat(programStats.movelist, p);
7660                 }
7661
7662                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7663                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7664                     DisplayMove(currentMove - 1);
7665                 }
7666                 return;
7667             }
7668         }
7669         else {
7670             buf1[0] = NULLCHAR;
7671
7672             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7673                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7674             {
7675                 ChessProgramStats cpstats;
7676
7677                 if (plyext != ' ' && plyext != '\t') {
7678                     time *= 100;
7679                 }
7680
7681                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7682                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7683                     curscore = -curscore;
7684                 }
7685
7686                 cpstats.depth = plylev;
7687                 cpstats.nodes = nodes;
7688                 cpstats.time = time;
7689                 cpstats.score = curscore;
7690                 cpstats.got_only_move = 0;
7691                 cpstats.movelist[0] = '\0';
7692
7693                 if (buf1[0] != NULLCHAR) {
7694                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7695                 }
7696
7697                 cpstats.ok_to_send = 0;
7698                 cpstats.line_is_book = 0;
7699                 cpstats.nr_moves = 0;
7700                 cpstats.moves_left = 0;
7701
7702                 SendProgramStatsToFrontend( cps, &cpstats );
7703             }
7704         }
7705     }
7706 }
7707
7708
7709 /* Parse a game score from the character string "game", and
7710    record it as the history of the current game.  The game
7711    score is NOT assumed to start from the standard position. 
7712    The display is not updated in any way.
7713    */
7714 void
7715 ParseGameHistory(game)
7716      char *game;
7717 {
7718     ChessMove moveType;
7719     int fromX, fromY, toX, toY, boardIndex;
7720     char promoChar;
7721     char *p, *q;
7722     char buf[MSG_SIZ];
7723
7724     if (appData.debugMode)
7725       fprintf(debugFP, "Parsing game history: %s\n", game);
7726
7727     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7728     gameInfo.site = StrSave(appData.icsHost);
7729     gameInfo.date = PGNDate();
7730     gameInfo.round = StrSave("-");
7731
7732     /* Parse out names of players */
7733     while (*game == ' ') game++;
7734     p = buf;
7735     while (*game != ' ') *p++ = *game++;
7736     *p = NULLCHAR;
7737     gameInfo.white = StrSave(buf);
7738     while (*game == ' ') game++;
7739     p = buf;
7740     while (*game != ' ' && *game != '\n') *p++ = *game++;
7741     *p = NULLCHAR;
7742     gameInfo.black = StrSave(buf);
7743
7744     /* Parse moves */
7745     boardIndex = blackPlaysFirst ? 1 : 0;
7746     yynewstr(game);
7747     for (;;) {
7748         yyboardindex = boardIndex;
7749         moveType = (ChessMove) yylex();
7750         switch (moveType) {
7751           case IllegalMove:             /* maybe suicide chess, etc. */
7752   if (appData.debugMode) {
7753     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7754     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7755     setbuf(debugFP, NULL);
7756   }
7757           case WhitePromotionChancellor:
7758           case BlackPromotionChancellor:
7759           case WhitePromotionArchbishop:
7760           case BlackPromotionArchbishop:
7761           case WhitePromotionQueen:
7762           case BlackPromotionQueen:
7763           case WhitePromotionRook:
7764           case BlackPromotionRook:
7765           case WhitePromotionBishop:
7766           case BlackPromotionBishop:
7767           case WhitePromotionKnight:
7768           case BlackPromotionKnight:
7769           case WhitePromotionKing:
7770           case BlackPromotionKing:
7771           case NormalMove:
7772           case WhiteCapturesEnPassant:
7773           case BlackCapturesEnPassant:
7774           case WhiteKingSideCastle:
7775           case WhiteQueenSideCastle:
7776           case BlackKingSideCastle:
7777           case BlackQueenSideCastle:
7778           case WhiteKingSideCastleWild:
7779           case WhiteQueenSideCastleWild:
7780           case BlackKingSideCastleWild:
7781           case BlackQueenSideCastleWild:
7782           /* PUSH Fabien */
7783           case WhiteHSideCastleFR:
7784           case WhiteASideCastleFR:
7785           case BlackHSideCastleFR:
7786           case BlackASideCastleFR:
7787           /* POP Fabien */
7788             fromX = currentMoveString[0] - AAA;
7789             fromY = currentMoveString[1] - ONE;
7790             toX = currentMoveString[2] - AAA;
7791             toY = currentMoveString[3] - ONE;
7792             promoChar = currentMoveString[4];
7793             break;
7794           case WhiteDrop:
7795           case BlackDrop:
7796             fromX = moveType == WhiteDrop ?
7797               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7798             (int) CharToPiece(ToLower(currentMoveString[0]));
7799             fromY = DROP_RANK;
7800             toX = currentMoveString[2] - AAA;
7801             toY = currentMoveString[3] - ONE;
7802             promoChar = NULLCHAR;
7803             break;
7804           case AmbiguousMove:
7805             /* bug? */
7806             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7807   if (appData.debugMode) {
7808     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7809     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7810     setbuf(debugFP, NULL);
7811   }
7812             DisplayError(buf, 0);
7813             return;
7814           case ImpossibleMove:
7815             /* bug? */
7816             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7817   if (appData.debugMode) {
7818     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7819     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7820     setbuf(debugFP, NULL);
7821   }
7822             DisplayError(buf, 0);
7823             return;
7824           case (ChessMove) 0:   /* end of file */
7825             if (boardIndex < backwardMostMove) {
7826                 /* Oops, gap.  How did that happen? */
7827                 DisplayError(_("Gap in move list"), 0);
7828                 return;
7829             }
7830             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7831             if (boardIndex > forwardMostMove) {
7832                 forwardMostMove = boardIndex;
7833             }
7834             return;
7835           case ElapsedTime:
7836             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7837                 strcat(parseList[boardIndex-1], " ");
7838                 strcat(parseList[boardIndex-1], yy_text);
7839             }
7840             continue;
7841           case Comment:
7842           case PGNTag:
7843           case NAG:
7844           default:
7845             /* ignore */
7846             continue;
7847           case WhiteWins:
7848           case BlackWins:
7849           case GameIsDrawn:
7850           case GameUnfinished:
7851             if (gameMode == IcsExamining) {
7852                 if (boardIndex < backwardMostMove) {
7853                     /* Oops, gap.  How did that happen? */
7854                     return;
7855                 }
7856                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7857                 return;
7858             }
7859             gameInfo.result = moveType;
7860             p = strchr(yy_text, '{');
7861             if (p == NULL) p = strchr(yy_text, '(');
7862             if (p == NULL) {
7863                 p = yy_text;
7864                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7865             } else {
7866                 q = strchr(p, *p == '{' ? '}' : ')');
7867                 if (q != NULL) *q = NULLCHAR;
7868                 p++;
7869             }
7870             gameInfo.resultDetails = StrSave(p);
7871             continue;
7872         }
7873         if (boardIndex >= forwardMostMove &&
7874             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7875             backwardMostMove = blackPlaysFirst ? 1 : 0;
7876             return;
7877         }
7878         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7879                                  fromY, fromX, toY, toX, promoChar,
7880                                  parseList[boardIndex]);
7881         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7882         /* currentMoveString is set as a side-effect of yylex */
7883         strcpy(moveList[boardIndex], currentMoveString);
7884         strcat(moveList[boardIndex], "\n");
7885         boardIndex++;
7886         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7887         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7888           case MT_NONE:
7889           case MT_STALEMATE:
7890           default:
7891             break;
7892           case MT_CHECK:
7893             if(gameInfo.variant != VariantShogi)
7894                 strcat(parseList[boardIndex - 1], "+");
7895             break;
7896           case MT_CHECKMATE:
7897           case MT_STAINMATE:
7898             strcat(parseList[boardIndex - 1], "#");
7899             break;
7900         }
7901     }
7902 }
7903
7904
7905 /* Apply a move to the given board  */
7906 void
7907 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7908      int fromX, fromY, toX, toY;
7909      int promoChar;
7910      Board board;
7911 {
7912   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7913   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7914
7915     /* [HGM] compute & store e.p. status and castling rights for new position */
7916     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7917     { int i;
7918
7919       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7920       oldEP = (signed char)board[EP_STATUS];
7921       board[EP_STATUS] = EP_NONE;
7922
7923       if( board[toY][toX] != EmptySquare ) 
7924            board[EP_STATUS] = EP_CAPTURE;  
7925
7926       if( board[fromY][fromX] == WhitePawn ) {
7927            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7928                board[EP_STATUS] = EP_PAWN_MOVE;
7929            if( toY-fromY==2) {
7930                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
7931                         gameInfo.variant != VariantBerolina || toX < fromX)
7932                       board[EP_STATUS] = toX | berolina;
7933                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7934                         gameInfo.variant != VariantBerolina || toX > fromX) 
7935                       board[EP_STATUS] = toX;
7936            }
7937       } else 
7938       if( board[fromY][fromX] == BlackPawn ) {
7939            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7940                board[EP_STATUS] = EP_PAWN_MOVE; 
7941            if( toY-fromY== -2) {
7942                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
7943                         gameInfo.variant != VariantBerolina || toX < fromX)
7944                       board[EP_STATUS] = toX | berolina;
7945                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7946                         gameInfo.variant != VariantBerolina || toX > fromX) 
7947                       board[EP_STATUS] = toX;
7948            }
7949        }
7950
7951        for(i=0; i<nrCastlingRights; i++) {
7952            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7953               board[CASTLING][i] == toX   && castlingRank[i] == toY   
7954              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7955        }
7956
7957     }
7958
7959   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7960   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7961        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7962          
7963   if (fromX == toX && fromY == toY) return;
7964
7965   if (fromY == DROP_RANK) {
7966         /* must be first */
7967         piece = board[toY][toX] = (ChessSquare) fromX;
7968   } else {
7969      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7970      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7971      if(gameInfo.variant == VariantKnightmate)
7972          king += (int) WhiteUnicorn - (int) WhiteKing;
7973
7974     /* Code added by Tord: */
7975     /* FRC castling assumed when king captures friendly rook. */
7976     if (board[fromY][fromX] == WhiteKing &&
7977              board[toY][toX] == WhiteRook) {
7978       board[fromY][fromX] = EmptySquare;
7979       board[toY][toX] = EmptySquare;
7980       if(toX > fromX) {
7981         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7982       } else {
7983         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7984       }
7985     } else if (board[fromY][fromX] == BlackKing &&
7986                board[toY][toX] == BlackRook) {
7987       board[fromY][fromX] = EmptySquare;
7988       board[toY][toX] = EmptySquare;
7989       if(toX > fromX) {
7990         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7991       } else {
7992         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7993       }
7994     /* End of code added by Tord */
7995
7996     } else if (board[fromY][fromX] == king
7997         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7998         && toY == fromY && toX > fromX+1) {
7999         board[fromY][fromX] = EmptySquare;
8000         board[toY][toX] = king;
8001         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8002         board[fromY][BOARD_RGHT-1] = EmptySquare;
8003     } else if (board[fromY][fromX] == king
8004         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8005                && toY == fromY && toX < fromX-1) {
8006         board[fromY][fromX] = EmptySquare;
8007         board[toY][toX] = king;
8008         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8009         board[fromY][BOARD_LEFT] = EmptySquare;
8010     } else if (board[fromY][fromX] == WhitePawn
8011                && toY >= BOARD_HEIGHT-promoRank
8012                && gameInfo.variant != VariantXiangqi
8013                ) {
8014         /* white pawn promotion */
8015         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8016         if (board[toY][toX] == EmptySquare) {
8017             board[toY][toX] = WhiteQueen;
8018         }
8019         if(gameInfo.variant==VariantBughouse ||
8020            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8021             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8022         board[fromY][fromX] = EmptySquare;
8023     } else if ((fromY == BOARD_HEIGHT-4)
8024                && (toX != fromX)
8025                && gameInfo.variant != VariantXiangqi
8026                && gameInfo.variant != VariantBerolina
8027                && (board[fromY][fromX] == WhitePawn)
8028                && (board[toY][toX] == EmptySquare)) {
8029         board[fromY][fromX] = EmptySquare;
8030         board[toY][toX] = WhitePawn;
8031         captured = board[toY - 1][toX];
8032         board[toY - 1][toX] = EmptySquare;
8033     } else if ((fromY == BOARD_HEIGHT-4)
8034                && (toX == fromX)
8035                && gameInfo.variant == VariantBerolina
8036                && (board[fromY][fromX] == WhitePawn)
8037                && (board[toY][toX] == EmptySquare)) {
8038         board[fromY][fromX] = EmptySquare;
8039         board[toY][toX] = WhitePawn;
8040         if(oldEP & EP_BEROLIN_A) {
8041                 captured = board[fromY][fromX-1];
8042                 board[fromY][fromX-1] = EmptySquare;
8043         }else{  captured = board[fromY][fromX+1];
8044                 board[fromY][fromX+1] = EmptySquare;
8045         }
8046     } else if (board[fromY][fromX] == king
8047         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8048                && toY == fromY && toX > fromX+1) {
8049         board[fromY][fromX] = EmptySquare;
8050         board[toY][toX] = king;
8051         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8052         board[fromY][BOARD_RGHT-1] = EmptySquare;
8053     } else if (board[fromY][fromX] == king
8054         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8055                && toY == fromY && toX < fromX-1) {
8056         board[fromY][fromX] = EmptySquare;
8057         board[toY][toX] = king;
8058         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8059         board[fromY][BOARD_LEFT] = EmptySquare;
8060     } else if (fromY == 7 && fromX == 3
8061                && board[fromY][fromX] == BlackKing
8062                && toY == 7 && toX == 5) {
8063         board[fromY][fromX] = EmptySquare;
8064         board[toY][toX] = BlackKing;
8065         board[fromY][7] = EmptySquare;
8066         board[toY][4] = BlackRook;
8067     } else if (fromY == 7 && fromX == 3
8068                && board[fromY][fromX] == BlackKing
8069                && toY == 7 && toX == 1) {
8070         board[fromY][fromX] = EmptySquare;
8071         board[toY][toX] = BlackKing;
8072         board[fromY][0] = EmptySquare;
8073         board[toY][2] = BlackRook;
8074     } else if (board[fromY][fromX] == BlackPawn
8075                && toY < promoRank
8076                && gameInfo.variant != VariantXiangqi
8077                ) {
8078         /* black pawn promotion */
8079         board[toY][toX] = CharToPiece(ToLower(promoChar));
8080         if (board[toY][toX] == EmptySquare) {
8081             board[toY][toX] = BlackQueen;
8082         }
8083         if(gameInfo.variant==VariantBughouse ||
8084            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8085             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8086         board[fromY][fromX] = EmptySquare;
8087     } else if ((fromY == 3)
8088                && (toX != fromX)
8089                && gameInfo.variant != VariantXiangqi
8090                && gameInfo.variant != VariantBerolina
8091                && (board[fromY][fromX] == BlackPawn)
8092                && (board[toY][toX] == EmptySquare)) {
8093         board[fromY][fromX] = EmptySquare;
8094         board[toY][toX] = BlackPawn;
8095         captured = board[toY + 1][toX];
8096         board[toY + 1][toX] = EmptySquare;
8097     } else if ((fromY == 3)
8098                && (toX == fromX)
8099                && gameInfo.variant == VariantBerolina
8100                && (board[fromY][fromX] == BlackPawn)
8101                && (board[toY][toX] == EmptySquare)) {
8102         board[fromY][fromX] = EmptySquare;
8103         board[toY][toX] = BlackPawn;
8104         if(oldEP & EP_BEROLIN_A) {
8105                 captured = board[fromY][fromX-1];
8106                 board[fromY][fromX-1] = EmptySquare;
8107         }else{  captured = board[fromY][fromX+1];
8108                 board[fromY][fromX+1] = EmptySquare;
8109         }
8110     } else {
8111         board[toY][toX] = board[fromY][fromX];
8112         board[fromY][fromX] = EmptySquare;
8113     }
8114
8115     /* [HGM] now we promote for Shogi, if needed */
8116     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8117         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8118   }
8119
8120     if (gameInfo.holdingsWidth != 0) {
8121
8122       /* !!A lot more code needs to be written to support holdings  */
8123       /* [HGM] OK, so I have written it. Holdings are stored in the */
8124       /* penultimate board files, so they are automaticlly stored   */
8125       /* in the game history.                                       */
8126       if (fromY == DROP_RANK) {
8127         /* Delete from holdings, by decreasing count */
8128         /* and erasing image if necessary            */
8129         p = (int) fromX;
8130         if(p < (int) BlackPawn) { /* white drop */
8131              p -= (int)WhitePawn;
8132                  p = PieceToNumber((ChessSquare)p);
8133              if(p >= gameInfo.holdingsSize) p = 0;
8134              if(--board[p][BOARD_WIDTH-2] <= 0)
8135                   board[p][BOARD_WIDTH-1] = EmptySquare;
8136              if((int)board[p][BOARD_WIDTH-2] < 0)
8137                         board[p][BOARD_WIDTH-2] = 0;
8138         } else {                  /* black drop */
8139              p -= (int)BlackPawn;
8140                  p = PieceToNumber((ChessSquare)p);
8141              if(p >= gameInfo.holdingsSize) p = 0;
8142              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8143                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8144              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8145                         board[BOARD_HEIGHT-1-p][1] = 0;
8146         }
8147       }
8148       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8149           && gameInfo.variant != VariantBughouse        ) {
8150         /* [HGM] holdings: Add to holdings, if holdings exist */
8151         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8152                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8153                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8154         }
8155         p = (int) captured;
8156         if (p >= (int) BlackPawn) {
8157           p -= (int)BlackPawn;
8158           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8159                   /* in Shogi restore piece to its original  first */
8160                   captured = (ChessSquare) (DEMOTED captured);
8161                   p = DEMOTED p;
8162           }
8163           p = PieceToNumber((ChessSquare)p);
8164           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8165           board[p][BOARD_WIDTH-2]++;
8166           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8167         } else {
8168           p -= (int)WhitePawn;
8169           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8170                   captured = (ChessSquare) (DEMOTED captured);
8171                   p = DEMOTED p;
8172           }
8173           p = PieceToNumber((ChessSquare)p);
8174           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8175           board[BOARD_HEIGHT-1-p][1]++;
8176           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8177         }
8178       }
8179     } else if (gameInfo.variant == VariantAtomic) {
8180       if (captured != EmptySquare) {
8181         int y, x;
8182         for (y = toY-1; y <= toY+1; y++) {
8183           for (x = toX-1; x <= toX+1; x++) {
8184             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8185                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8186               board[y][x] = EmptySquare;
8187             }
8188           }
8189         }
8190         board[toY][toX] = EmptySquare;
8191       }
8192     }
8193     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8194         /* [HGM] Shogi promotions */
8195         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8196     }
8197
8198     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8199                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8200         // [HGM] superchess: take promotion piece out of holdings
8201         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8202         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8203             if(!--board[k][BOARD_WIDTH-2])
8204                 board[k][BOARD_WIDTH-1] = EmptySquare;
8205         } else {
8206             if(!--board[BOARD_HEIGHT-1-k][1])
8207                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8208         }
8209     }
8210
8211 }
8212
8213 /* Updates forwardMostMove */
8214 void
8215 MakeMove(fromX, fromY, toX, toY, promoChar)
8216      int fromX, fromY, toX, toY;
8217      int promoChar;
8218 {
8219 //    forwardMostMove++; // [HGM] bare: moved downstream
8220
8221     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8222         int timeLeft; static int lastLoadFlag=0; int king, piece;
8223         piece = boards[forwardMostMove][fromY][fromX];
8224         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8225         if(gameInfo.variant == VariantKnightmate)
8226             king += (int) WhiteUnicorn - (int) WhiteKing;
8227         if(forwardMostMove == 0) {
8228             if(blackPlaysFirst) 
8229                 fprintf(serverMoves, "%s;", second.tidy);
8230             fprintf(serverMoves, "%s;", first.tidy);
8231             if(!blackPlaysFirst) 
8232                 fprintf(serverMoves, "%s;", second.tidy);
8233         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8234         lastLoadFlag = loadFlag;
8235         // print base move
8236         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8237         // print castling suffix
8238         if( toY == fromY && piece == king ) {
8239             if(toX-fromX > 1)
8240                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8241             if(fromX-toX >1)
8242                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8243         }
8244         // e.p. suffix
8245         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8246              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8247              boards[forwardMostMove][toY][toX] == EmptySquare
8248              && fromX != toX )
8249                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8250         // promotion suffix
8251         if(promoChar != NULLCHAR)
8252                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8253         if(!loadFlag) {
8254             fprintf(serverMoves, "/%d/%d",
8255                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8256             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8257             else                      timeLeft = blackTimeRemaining/1000;
8258             fprintf(serverMoves, "/%d", timeLeft);
8259         }
8260         fflush(serverMoves);
8261     }
8262
8263     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8264       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8265                         0, 1);
8266       return;
8267     }
8268     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8269     if (commentList[forwardMostMove+1] != NULL) {
8270         free(commentList[forwardMostMove+1]);
8271         commentList[forwardMostMove+1] = NULL;
8272     }
8273     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8274     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8275     forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8276     SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8277     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8278     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8279     gameInfo.result = GameUnfinished;
8280     if (gameInfo.resultDetails != NULL) {
8281         free(gameInfo.resultDetails);
8282         gameInfo.resultDetails = NULL;
8283     }
8284     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8285                               moveList[forwardMostMove - 1]);
8286     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8287                              PosFlags(forwardMostMove - 1),
8288                              fromY, fromX, toY, toX, promoChar,
8289                              parseList[forwardMostMove - 1]);
8290     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8291       case MT_NONE:
8292       case MT_STALEMATE:
8293       default:
8294         break;
8295       case MT_CHECK:
8296         if(gameInfo.variant != VariantShogi)
8297             strcat(parseList[forwardMostMove - 1], "+");
8298         break;
8299       case MT_CHECKMATE:
8300       case MT_STAINMATE:
8301         strcat(parseList[forwardMostMove - 1], "#");
8302         break;
8303     }
8304     if (appData.debugMode) {
8305         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8306     }
8307
8308 }
8309
8310 /* Updates currentMove if not pausing */
8311 void
8312 ShowMove(fromX, fromY, toX, toY)
8313 {
8314     int instant = (gameMode == PlayFromGameFile) ?
8315         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8316     if(appData.noGUI) return;
8317     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8318         if (!instant) {
8319             if (forwardMostMove == currentMove + 1) {
8320                 AnimateMove(boards[forwardMostMove - 1],
8321                             fromX, fromY, toX, toY);
8322             }
8323             if (appData.highlightLastMove) {
8324                 SetHighlights(fromX, fromY, toX, toY);
8325             }
8326         }
8327         currentMove = forwardMostMove;
8328     }
8329
8330     if (instant) return;
8331
8332     DisplayMove(currentMove - 1);
8333     DrawPosition(FALSE, boards[currentMove]);
8334     DisplayBothClocks();
8335     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8336 }
8337
8338 void SendEgtPath(ChessProgramState *cps)
8339 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8340         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8341
8342         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8343
8344         while(*p) {
8345             char c, *q = name+1, *r, *s;
8346
8347             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8348             while(*p && *p != ',') *q++ = *p++;
8349             *q++ = ':'; *q = 0;
8350             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8351                 strcmp(name, ",nalimov:") == 0 ) {
8352                 // take nalimov path from the menu-changeable option first, if it is defined
8353                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8354                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8355             } else
8356             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8357                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8358                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8359                 s = r = StrStr(s, ":") + 1; // beginning of path info
8360                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8361                 c = *r; *r = 0;             // temporarily null-terminate path info
8362                     *--q = 0;               // strip of trailig ':' from name
8363                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8364                 *r = c;
8365                 SendToProgram(buf,cps);     // send egtbpath command for this format
8366             }
8367             if(*p == ',') p++; // read away comma to position for next format name
8368         }
8369 }
8370
8371 void
8372 InitChessProgram(cps, setup)
8373      ChessProgramState *cps;
8374      int setup; /* [HGM] needed to setup FRC opening position */
8375 {
8376     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8377     if (appData.noChessProgram) return;
8378     hintRequested = FALSE;
8379     bookRequested = FALSE;
8380
8381     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8382     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8383     if(cps->memSize) { /* [HGM] memory */
8384         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8385         SendToProgram(buf, cps);
8386     }
8387     SendEgtPath(cps); /* [HGM] EGT */
8388     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8389         sprintf(buf, "cores %d\n", appData.smpCores);
8390         SendToProgram(buf, cps);
8391     }
8392
8393     SendToProgram(cps->initString, cps);
8394     if (gameInfo.variant != VariantNormal &&
8395         gameInfo.variant != VariantLoadable
8396         /* [HGM] also send variant if board size non-standard */
8397         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8398                                             ) {
8399       char *v = VariantName(gameInfo.variant);
8400       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8401         /* [HGM] in protocol 1 we have to assume all variants valid */
8402         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8403         DisplayFatalError(buf, 0, 1);
8404         return;
8405       }
8406
8407       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8408       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8409       if( gameInfo.variant == VariantXiangqi )
8410            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8411       if( gameInfo.variant == VariantShogi )
8412            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8413       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8414            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8415       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8416                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8417            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8418       if( gameInfo.variant == VariantCourier )
8419            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8420       if( gameInfo.variant == VariantSuper )
8421            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8422       if( gameInfo.variant == VariantGreat )
8423            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8424
8425       if(overruled) {
8426            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8427                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8428            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8429            if(StrStr(cps->variants, b) == NULL) { 
8430                // specific sized variant not known, check if general sizing allowed
8431                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8432                    if(StrStr(cps->variants, "boardsize") == NULL) {
8433                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8434                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8435                        DisplayFatalError(buf, 0, 1);
8436                        return;
8437                    }
8438                    /* [HGM] here we really should compare with the maximum supported board size */
8439                }
8440            }
8441       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8442       sprintf(buf, "variant %s\n", b);
8443       SendToProgram(buf, cps);
8444     }
8445     currentlyInitializedVariant = gameInfo.variant;
8446
8447     /* [HGM] send opening position in FRC to first engine */
8448     if(setup) {
8449           SendToProgram("force\n", cps);
8450           SendBoard(cps, 0);
8451           /* engine is now in force mode! Set flag to wake it up after first move. */
8452           setboardSpoiledMachineBlack = 1;
8453     }
8454
8455     if (cps->sendICS) {
8456       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8457       SendToProgram(buf, cps);
8458     }
8459     cps->maybeThinking = FALSE;
8460     cps->offeredDraw = 0;
8461     if (!appData.icsActive) {
8462         SendTimeControl(cps, movesPerSession, timeControl,
8463                         timeIncrement, appData.searchDepth,
8464                         searchTime);
8465     }
8466     if (appData.showThinking 
8467         // [HGM] thinking: four options require thinking output to be sent
8468         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8469                                 ) {
8470         SendToProgram("post\n", cps);
8471     }
8472     SendToProgram("hard\n", cps);
8473     if (!appData.ponderNextMove) {
8474         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8475            it without being sure what state we are in first.  "hard"
8476            is not a toggle, so that one is OK.
8477          */
8478         SendToProgram("easy\n", cps);
8479     }
8480     if (cps->usePing) {
8481       sprintf(buf, "ping %d\n", ++cps->lastPing);
8482       SendToProgram(buf, cps);
8483     }
8484     cps->initDone = TRUE;
8485 }   
8486
8487
8488 void
8489 StartChessProgram(cps)
8490      ChessProgramState *cps;
8491 {
8492     char buf[MSG_SIZ];
8493     int err;
8494
8495     if (appData.noChessProgram) return;
8496     cps->initDone = FALSE;
8497
8498     if (strcmp(cps->host, "localhost") == 0) {
8499         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8500     } else if (*appData.remoteShell == NULLCHAR) {
8501         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8502     } else {
8503         if (*appData.remoteUser == NULLCHAR) {
8504           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8505                     cps->program);
8506         } else {
8507           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8508                     cps->host, appData.remoteUser, cps->program);
8509         }
8510         err = StartChildProcess(buf, "", &cps->pr);
8511     }
8512     
8513     if (err != 0) {
8514         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8515         DisplayFatalError(buf, err, 1);
8516         cps->pr = NoProc;
8517         cps->isr = NULL;
8518         return;
8519     }
8520     
8521     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8522     if (cps->protocolVersion > 1) {
8523       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8524       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8525       cps->comboCnt = 0;  //                and values of combo boxes
8526       SendToProgram(buf, cps);
8527     } else {
8528       SendToProgram("xboard\n", cps);
8529     }
8530 }
8531
8532
8533 void
8534 TwoMachinesEventIfReady P((void))
8535 {
8536   if (first.lastPing != first.lastPong) {
8537     DisplayMessage("", _("Waiting for first chess program"));
8538     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8539     return;
8540   }
8541   if (second.lastPing != second.lastPong) {
8542     DisplayMessage("", _("Waiting for second chess program"));
8543     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8544     return;
8545   }
8546   ThawUI();
8547   TwoMachinesEvent();
8548 }
8549
8550 void
8551 NextMatchGame P((void))
8552 {
8553     int index; /* [HGM] autoinc: step load index during match */
8554     Reset(FALSE, TRUE);
8555     if (*appData.loadGameFile != NULLCHAR) {
8556         index = appData.loadGameIndex;
8557         if(index < 0) { // [HGM] autoinc
8558             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8559             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8560         } 
8561         LoadGameFromFile(appData.loadGameFile,
8562                          index,
8563                          appData.loadGameFile, FALSE);
8564     } else if (*appData.loadPositionFile != NULLCHAR) {
8565         index = appData.loadPositionIndex;
8566         if(index < 0) { // [HGM] autoinc
8567             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8568             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8569         } 
8570         LoadPositionFromFile(appData.loadPositionFile,
8571                              index,
8572                              appData.loadPositionFile);
8573     }
8574     TwoMachinesEventIfReady();
8575 }
8576
8577 void UserAdjudicationEvent( int result )
8578 {
8579     ChessMove gameResult = GameIsDrawn;
8580
8581     if( result > 0 ) {
8582         gameResult = WhiteWins;
8583     }
8584     else if( result < 0 ) {
8585         gameResult = BlackWins;
8586     }
8587
8588     if( gameMode == TwoMachinesPlay ) {
8589         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8590     }
8591 }
8592
8593
8594 // [HGM] save: calculate checksum of game to make games easily identifiable
8595 int StringCheckSum(char *s)
8596 {
8597         int i = 0;
8598         if(s==NULL) return 0;
8599         while(*s) i = i*259 + *s++;
8600         return i;
8601 }
8602
8603 int GameCheckSum()
8604 {
8605         int i, sum=0;
8606         for(i=backwardMostMove; i<forwardMostMove; i++) {
8607                 sum += pvInfoList[i].depth;
8608                 sum += StringCheckSum(parseList[i]);
8609                 sum += StringCheckSum(commentList[i]);
8610                 sum *= 261;
8611         }
8612         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8613         return sum + StringCheckSum(commentList[i]);
8614 } // end of save patch
8615
8616 void
8617 GameEnds(result, resultDetails, whosays)
8618      ChessMove result;
8619      char *resultDetails;
8620      int whosays;
8621 {
8622     GameMode nextGameMode;
8623     int isIcsGame;
8624     char buf[MSG_SIZ];
8625
8626     if(endingGame) return; /* [HGM] crash: forbid recursion */
8627     endingGame = 1;
8628
8629     if (appData.debugMode) {
8630       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8631               result, resultDetails ? resultDetails : "(null)", whosays);
8632     }
8633
8634     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8635         /* If we are playing on ICS, the server decides when the
8636            game is over, but the engine can offer to draw, claim 
8637            a draw, or resign. 
8638          */
8639 #if ZIPPY
8640         if (appData.zippyPlay && first.initDone) {
8641             if (result == GameIsDrawn) {
8642                 /* In case draw still needs to be claimed */
8643                 SendToICS(ics_prefix);
8644                 SendToICS("draw\n");
8645             } else if (StrCaseStr(resultDetails, "resign")) {
8646                 SendToICS(ics_prefix);
8647                 SendToICS("resign\n");
8648             }
8649         }
8650 #endif
8651         endingGame = 0; /* [HGM] crash */
8652         return;
8653     }
8654
8655     /* If we're loading the game from a file, stop */
8656     if (whosays == GE_FILE) {
8657       (void) StopLoadGameTimer();
8658       gameFileFP = NULL;
8659     }
8660
8661     /* Cancel draw offers */
8662     first.offeredDraw = second.offeredDraw = 0;
8663
8664     /* If this is an ICS game, only ICS can really say it's done;
8665        if not, anyone can. */
8666     isIcsGame = (gameMode == IcsPlayingWhite || 
8667                  gameMode == IcsPlayingBlack || 
8668                  gameMode == IcsObserving    || 
8669                  gameMode == IcsExamining);
8670
8671     if (!isIcsGame || whosays == GE_ICS) {
8672         /* OK -- not an ICS game, or ICS said it was done */
8673         StopClocks();
8674         if (!isIcsGame && !appData.noChessProgram) 
8675           SetUserThinkingEnables();
8676     
8677         /* [HGM] if a machine claims the game end we verify this claim */
8678         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8679             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8680                 char claimer;
8681                 ChessMove trueResult = (ChessMove) -1;
8682
8683                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8684                                             first.twoMachinesColor[0] :
8685                                             second.twoMachinesColor[0] ;
8686
8687                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8688                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8689                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8690                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8691                 } else
8692                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8693                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8694                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8695                 } else
8696                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8697                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8698                 }
8699
8700                 // now verify win claims, but not in drop games, as we don't understand those yet
8701                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8702                                                  || gameInfo.variant == VariantGreat) &&
8703                     (result == WhiteWins && claimer == 'w' ||
8704                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8705                       if (appData.debugMode) {
8706                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8707                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8708                       }
8709                       if(result != trueResult) {
8710                               sprintf(buf, "False win claim: '%s'", resultDetails);
8711                               result = claimer == 'w' ? BlackWins : WhiteWins;
8712                               resultDetails = buf;
8713                       }
8714                 } else
8715                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8716                     && (forwardMostMove <= backwardMostMove ||
8717                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8718                         (claimer=='b')==(forwardMostMove&1))
8719                                                                                   ) {
8720                       /* [HGM] verify: draws that were not flagged are false claims */
8721                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8722                       result = claimer == 'w' ? BlackWins : WhiteWins;
8723                       resultDetails = buf;
8724                 }
8725                 /* (Claiming a loss is accepted no questions asked!) */
8726             }
8727             /* [HGM] bare: don't allow bare King to win */
8728             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8729                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8730                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8731                && result != GameIsDrawn)
8732             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8733                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8734                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8735                         if(p >= 0 && p <= (int)WhiteKing) k++;
8736                 }
8737                 if (appData.debugMode) {
8738                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8739                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8740                 }
8741                 if(k <= 1) {
8742                         result = GameIsDrawn;
8743                         sprintf(buf, "%s but bare king", resultDetails);
8744                         resultDetails = buf;
8745                 }
8746             }
8747         }
8748
8749
8750         if(serverMoves != NULL && !loadFlag) { char c = '=';
8751             if(result==WhiteWins) c = '+';
8752             if(result==BlackWins) c = '-';
8753             if(resultDetails != NULL)
8754                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8755         }
8756         if (resultDetails != NULL) {
8757             gameInfo.result = result;
8758             gameInfo.resultDetails = StrSave(resultDetails);
8759
8760             /* display last move only if game was not loaded from file */
8761             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8762                 DisplayMove(currentMove - 1);
8763     
8764             if (forwardMostMove != 0) {
8765                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8766                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8767                                                                 ) {
8768                     if (*appData.saveGameFile != NULLCHAR) {
8769                         SaveGameToFile(appData.saveGameFile, TRUE);
8770                     } else if (appData.autoSaveGames) {
8771                         AutoSaveGame();
8772                     }
8773                     if (*appData.savePositionFile != NULLCHAR) {
8774                         SavePositionToFile(appData.savePositionFile);
8775                     }
8776                 }
8777             }
8778
8779             /* Tell program how game ended in case it is learning */
8780             /* [HGM] Moved this to after saving the PGN, just in case */
8781             /* engine died and we got here through time loss. In that */
8782             /* case we will get a fatal error writing the pipe, which */
8783             /* would otherwise lose us the PGN.                       */
8784             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8785             /* output during GameEnds should never be fatal anymore   */
8786             if (gameMode == MachinePlaysWhite ||
8787                 gameMode == MachinePlaysBlack ||
8788                 gameMode == TwoMachinesPlay ||
8789                 gameMode == IcsPlayingWhite ||
8790                 gameMode == IcsPlayingBlack ||
8791                 gameMode == BeginningOfGame) {
8792                 char buf[MSG_SIZ];
8793                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8794                         resultDetails);
8795                 if (first.pr != NoProc) {
8796                     SendToProgram(buf, &first);
8797                 }
8798                 if (second.pr != NoProc &&
8799                     gameMode == TwoMachinesPlay) {
8800                     SendToProgram(buf, &second);
8801                 }
8802             }
8803         }
8804
8805         if (appData.icsActive) {
8806             if (appData.quietPlay &&
8807                 (gameMode == IcsPlayingWhite ||
8808                  gameMode == IcsPlayingBlack)) {
8809                 SendToICS(ics_prefix);
8810                 SendToICS("set shout 1\n");
8811             }
8812             nextGameMode = IcsIdle;
8813             ics_user_moved = FALSE;
8814             /* clean up premove.  It's ugly when the game has ended and the
8815              * premove highlights are still on the board.
8816              */
8817             if (gotPremove) {
8818               gotPremove = FALSE;
8819               ClearPremoveHighlights();
8820               DrawPosition(FALSE, boards[currentMove]);
8821             }
8822             if (whosays == GE_ICS) {
8823                 switch (result) {
8824                 case WhiteWins:
8825                     if (gameMode == IcsPlayingWhite)
8826                         PlayIcsWinSound();
8827                     else if(gameMode == IcsPlayingBlack)
8828                         PlayIcsLossSound();
8829                     break;
8830                 case BlackWins:
8831                     if (gameMode == IcsPlayingBlack)
8832                         PlayIcsWinSound();
8833                     else if(gameMode == IcsPlayingWhite)
8834                         PlayIcsLossSound();
8835                     break;
8836                 case GameIsDrawn:
8837                     PlayIcsDrawSound();
8838                     break;
8839                 default:
8840                     PlayIcsUnfinishedSound();
8841                 }
8842             }
8843         } else if (gameMode == EditGame ||
8844                    gameMode == PlayFromGameFile || 
8845                    gameMode == AnalyzeMode || 
8846                    gameMode == AnalyzeFile) {
8847             nextGameMode = gameMode;
8848         } else {
8849             nextGameMode = EndOfGame;
8850         }
8851         pausing = FALSE;
8852         ModeHighlight();
8853     } else {
8854         nextGameMode = gameMode;
8855     }
8856
8857     if (appData.noChessProgram) {
8858         gameMode = nextGameMode;
8859         ModeHighlight();
8860         endingGame = 0; /* [HGM] crash */
8861         return;
8862     }
8863
8864     if (first.reuse) {
8865         /* Put first chess program into idle state */
8866         if (first.pr != NoProc &&
8867             (gameMode == MachinePlaysWhite ||
8868              gameMode == MachinePlaysBlack ||
8869              gameMode == TwoMachinesPlay ||
8870              gameMode == IcsPlayingWhite ||
8871              gameMode == IcsPlayingBlack ||
8872              gameMode == BeginningOfGame)) {
8873             SendToProgram("force\n", &first);
8874             if (first.usePing) {
8875               char buf[MSG_SIZ];
8876               sprintf(buf, "ping %d\n", ++first.lastPing);
8877               SendToProgram(buf, &first);
8878             }
8879         }
8880     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8881         /* Kill off first chess program */
8882         if (first.isr != NULL)
8883           RemoveInputSource(first.isr);
8884         first.isr = NULL;
8885     
8886         if (first.pr != NoProc) {
8887             ExitAnalyzeMode();
8888             DoSleep( appData.delayBeforeQuit );
8889             SendToProgram("quit\n", &first);
8890             DoSleep( appData.delayAfterQuit );
8891             DestroyChildProcess(first.pr, first.useSigterm);
8892         }
8893         first.pr = NoProc;
8894     }
8895     if (second.reuse) {
8896         /* Put second chess program into idle state */
8897         if (second.pr != NoProc &&
8898             gameMode == TwoMachinesPlay) {
8899             SendToProgram("force\n", &second);
8900             if (second.usePing) {
8901               char buf[MSG_SIZ];
8902               sprintf(buf, "ping %d\n", ++second.lastPing);
8903               SendToProgram(buf, &second);
8904             }
8905         }
8906     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8907         /* Kill off second chess program */
8908         if (second.isr != NULL)
8909           RemoveInputSource(second.isr);
8910         second.isr = NULL;
8911     
8912         if (second.pr != NoProc) {
8913             DoSleep( appData.delayBeforeQuit );
8914             SendToProgram("quit\n", &second);
8915             DoSleep( appData.delayAfterQuit );
8916             DestroyChildProcess(second.pr, second.useSigterm);
8917         }
8918         second.pr = NoProc;
8919     }
8920
8921     if (matchMode && gameMode == TwoMachinesPlay) {
8922         switch (result) {
8923         case WhiteWins:
8924           if (first.twoMachinesColor[0] == 'w') {
8925             first.matchWins++;
8926           } else {
8927             second.matchWins++;
8928           }
8929           break;
8930         case BlackWins:
8931           if (first.twoMachinesColor[0] == 'b') {
8932             first.matchWins++;
8933           } else {
8934             second.matchWins++;
8935           }
8936           break;
8937         default:
8938           break;
8939         }
8940         if (matchGame < appData.matchGames) {
8941             char *tmp;
8942             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8943                 tmp = first.twoMachinesColor;
8944                 first.twoMachinesColor = second.twoMachinesColor;
8945                 second.twoMachinesColor = tmp;
8946             }
8947             gameMode = nextGameMode;
8948             matchGame++;
8949             if(appData.matchPause>10000 || appData.matchPause<10)
8950                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8951             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8952             endingGame = 0; /* [HGM] crash */
8953             return;
8954         } else {
8955             char buf[MSG_SIZ];
8956             gameMode = nextGameMode;
8957             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8958                     first.tidy, second.tidy,
8959                     first.matchWins, second.matchWins,
8960                     appData.matchGames - (first.matchWins + second.matchWins));
8961             DisplayFatalError(buf, 0, 0);
8962         }
8963     }
8964     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8965         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8966       ExitAnalyzeMode();
8967     gameMode = nextGameMode;
8968     ModeHighlight();
8969     endingGame = 0;  /* [HGM] crash */
8970 }
8971
8972 /* Assumes program was just initialized (initString sent).
8973    Leaves program in force mode. */
8974 void
8975 FeedMovesToProgram(cps, upto) 
8976      ChessProgramState *cps;
8977      int upto;
8978 {
8979     int i;
8980     
8981     if (appData.debugMode)
8982       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8983               startedFromSetupPosition ? "position and " : "",
8984               backwardMostMove, upto, cps->which);
8985     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8986         // [HGM] variantswitch: make engine aware of new variant
8987         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8988                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8989         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8990         SendToProgram(buf, cps);
8991         currentlyInitializedVariant = gameInfo.variant;
8992     }
8993     SendToProgram("force\n", cps);
8994     if (startedFromSetupPosition) {
8995         SendBoard(cps, backwardMostMove);
8996     if (appData.debugMode) {
8997         fprintf(debugFP, "feedMoves\n");
8998     }
8999     }
9000     for (i = backwardMostMove; i < upto; i++) {
9001         SendMoveToProgram(i, cps);
9002     }
9003 }
9004
9005
9006 void
9007 ResurrectChessProgram()
9008 {
9009      /* The chess program may have exited.
9010         If so, restart it and feed it all the moves made so far. */
9011
9012     if (appData.noChessProgram || first.pr != NoProc) return;
9013     
9014     StartChessProgram(&first);
9015     InitChessProgram(&first, FALSE);
9016     FeedMovesToProgram(&first, currentMove);
9017
9018     if (!first.sendTime) {
9019         /* can't tell gnuchess what its clock should read,
9020            so we bow to its notion. */
9021         ResetClocks();
9022         timeRemaining[0][currentMove] = whiteTimeRemaining;
9023         timeRemaining[1][currentMove] = blackTimeRemaining;
9024     }
9025
9026     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9027                 appData.icsEngineAnalyze) && first.analysisSupport) {
9028       SendToProgram("analyze\n", &first);
9029       first.analyzing = TRUE;
9030     }
9031 }
9032
9033 /*
9034  * Button procedures
9035  */
9036 void
9037 Reset(redraw, init)
9038      int redraw, init;
9039 {
9040     int i;
9041
9042     if (appData.debugMode) {
9043         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9044                 redraw, init, gameMode);
9045     }
9046     CleanupTail(); // [HGM] vari: delete any stored variations
9047     pausing = pauseExamInvalid = FALSE;
9048     startedFromSetupPosition = blackPlaysFirst = FALSE;
9049     firstMove = TRUE;
9050     whiteFlag = blackFlag = FALSE;
9051     userOfferedDraw = FALSE;
9052     hintRequested = bookRequested = FALSE;
9053     first.maybeThinking = FALSE;
9054     second.maybeThinking = FALSE;
9055     first.bookSuspend = FALSE; // [HGM] book
9056     second.bookSuspend = FALSE;
9057     thinkOutput[0] = NULLCHAR;
9058     lastHint[0] = NULLCHAR;
9059     ClearGameInfo(&gameInfo);
9060     gameInfo.variant = StringToVariant(appData.variant);
9061     ics_user_moved = ics_clock_paused = FALSE;
9062     ics_getting_history = H_FALSE;
9063     ics_gamenum = -1;
9064     white_holding[0] = black_holding[0] = NULLCHAR;
9065     ClearProgramStats();
9066     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9067     
9068     ResetFrontEnd();
9069     ClearHighlights();
9070     flipView = appData.flipView;
9071     ClearPremoveHighlights();
9072     gotPremove = FALSE;
9073     alarmSounded = FALSE;
9074
9075     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9076     if(appData.serverMovesName != NULL) {
9077         /* [HGM] prepare to make moves file for broadcasting */
9078         clock_t t = clock();
9079         if(serverMoves != NULL) fclose(serverMoves);
9080         serverMoves = fopen(appData.serverMovesName, "r");
9081         if(serverMoves != NULL) {
9082             fclose(serverMoves);
9083             /* delay 15 sec before overwriting, so all clients can see end */
9084             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9085         }
9086         serverMoves = fopen(appData.serverMovesName, "w");
9087     }
9088
9089     ExitAnalyzeMode();
9090     gameMode = BeginningOfGame;
9091     ModeHighlight();
9092     if(appData.icsActive) gameInfo.variant = VariantNormal;
9093     currentMove = forwardMostMove = backwardMostMove = 0;
9094     InitPosition(redraw);
9095     for (i = 0; i < MAX_MOVES; i++) {
9096         if (commentList[i] != NULL) {
9097             free(commentList[i]);
9098             commentList[i] = NULL;
9099         }
9100     }
9101     ResetClocks();
9102     timeRemaining[0][0] = whiteTimeRemaining;
9103     timeRemaining[1][0] = blackTimeRemaining;
9104     if (first.pr == NULL) {
9105         StartChessProgram(&first);
9106     }
9107     if (init) {
9108             InitChessProgram(&first, startedFromSetupPosition);
9109     }
9110     DisplayTitle("");
9111     DisplayMessage("", "");
9112     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9113     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9114 }
9115
9116 void
9117 AutoPlayGameLoop()
9118 {
9119     for (;;) {
9120         if (!AutoPlayOneMove())
9121           return;
9122         if (matchMode || appData.timeDelay == 0)
9123           continue;
9124         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9125           return;
9126         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9127         break;
9128     }
9129 }
9130
9131
9132 int
9133 AutoPlayOneMove()
9134 {
9135     int fromX, fromY, toX, toY;
9136
9137     if (appData.debugMode) {
9138       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9139     }
9140
9141     if (gameMode != PlayFromGameFile)
9142       return FALSE;
9143
9144     if (currentMove >= forwardMostMove) {
9145       gameMode = EditGame;
9146       ModeHighlight();
9147
9148       /* [AS] Clear current move marker at the end of a game */
9149       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9150
9151       return FALSE;
9152     }
9153     
9154     toX = moveList[currentMove][2] - AAA;
9155     toY = moveList[currentMove][3] - ONE;
9156
9157     if (moveList[currentMove][1] == '@') {
9158         if (appData.highlightLastMove) {
9159             SetHighlights(-1, -1, toX, toY);
9160         }
9161     } else {
9162         fromX = moveList[currentMove][0] - AAA;
9163         fromY = moveList[currentMove][1] - ONE;
9164
9165         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9166
9167         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9168
9169         if (appData.highlightLastMove) {
9170             SetHighlights(fromX, fromY, toX, toY);
9171         }
9172     }
9173     DisplayMove(currentMove);
9174     SendMoveToProgram(currentMove++, &first);
9175     DisplayBothClocks();
9176     DrawPosition(FALSE, boards[currentMove]);
9177     // [HGM] PV info: always display, routine tests if empty
9178     DisplayComment(currentMove - 1, commentList[currentMove]);
9179     return TRUE;
9180 }
9181
9182
9183 int
9184 LoadGameOneMove(readAhead)
9185      ChessMove readAhead;
9186 {
9187     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9188     char promoChar = NULLCHAR;
9189     ChessMove moveType;
9190     char move[MSG_SIZ];
9191     char *p, *q;
9192     
9193     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9194         gameMode != AnalyzeMode && gameMode != Training) {
9195         gameFileFP = NULL;
9196         return FALSE;
9197     }
9198     
9199     yyboardindex = forwardMostMove;
9200     if (readAhead != (ChessMove)0) {
9201       moveType = readAhead;
9202     } else {
9203       if (gameFileFP == NULL)
9204           return FALSE;
9205       moveType = (ChessMove) yylex();
9206     }
9207     
9208     done = FALSE;
9209     switch (moveType) {
9210       case Comment:
9211         if (appData.debugMode) 
9212           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9213         p = yy_text;
9214
9215         /* append the comment but don't display it */
9216         AppendComment(currentMove, p, FALSE);
9217         return TRUE;
9218
9219       case WhiteCapturesEnPassant:
9220       case BlackCapturesEnPassant:
9221       case WhitePromotionChancellor:
9222       case BlackPromotionChancellor:
9223       case WhitePromotionArchbishop:
9224       case BlackPromotionArchbishop:
9225       case WhitePromotionCentaur:
9226       case BlackPromotionCentaur:
9227       case WhitePromotionQueen:
9228       case BlackPromotionQueen:
9229       case WhitePromotionRook:
9230       case BlackPromotionRook:
9231       case WhitePromotionBishop:
9232       case BlackPromotionBishop:
9233       case WhitePromotionKnight:
9234       case BlackPromotionKnight:
9235       case WhitePromotionKing:
9236       case BlackPromotionKing:
9237       case NormalMove:
9238       case WhiteKingSideCastle:
9239       case WhiteQueenSideCastle:
9240       case BlackKingSideCastle:
9241       case BlackQueenSideCastle:
9242       case WhiteKingSideCastleWild:
9243       case WhiteQueenSideCastleWild:
9244       case BlackKingSideCastleWild:
9245       case BlackQueenSideCastleWild:
9246       /* PUSH Fabien */
9247       case WhiteHSideCastleFR:
9248       case WhiteASideCastleFR:
9249       case BlackHSideCastleFR:
9250       case BlackASideCastleFR:
9251       /* POP Fabien */
9252         if (appData.debugMode)
9253           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9254         fromX = currentMoveString[0] - AAA;
9255         fromY = currentMoveString[1] - ONE;
9256         toX = currentMoveString[2] - AAA;
9257         toY = currentMoveString[3] - ONE;
9258         promoChar = currentMoveString[4];
9259         break;
9260
9261       case WhiteDrop:
9262       case BlackDrop:
9263         if (appData.debugMode)
9264           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9265         fromX = moveType == WhiteDrop ?
9266           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9267         (int) CharToPiece(ToLower(currentMoveString[0]));
9268         fromY = DROP_RANK;
9269         toX = currentMoveString[2] - AAA;
9270         toY = currentMoveString[3] - ONE;
9271         break;
9272
9273       case WhiteWins:
9274       case BlackWins:
9275       case GameIsDrawn:
9276       case GameUnfinished:
9277         if (appData.debugMode)
9278           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9279         p = strchr(yy_text, '{');
9280         if (p == NULL) p = strchr(yy_text, '(');
9281         if (p == NULL) {
9282             p = yy_text;
9283             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9284         } else {
9285             q = strchr(p, *p == '{' ? '}' : ')');
9286             if (q != NULL) *q = NULLCHAR;
9287             p++;
9288         }
9289         GameEnds(moveType, p, GE_FILE);
9290         done = TRUE;
9291         if (cmailMsgLoaded) {
9292             ClearHighlights();
9293             flipView = WhiteOnMove(currentMove);
9294             if (moveType == GameUnfinished) flipView = !flipView;
9295             if (appData.debugMode)
9296               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9297         }
9298         break;
9299
9300       case (ChessMove) 0:       /* end of file */
9301         if (appData.debugMode)
9302           fprintf(debugFP, "Parser hit end of file\n");
9303         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9304           case MT_NONE:
9305           case MT_CHECK:
9306             break;
9307           case MT_CHECKMATE:
9308           case MT_STAINMATE:
9309             if (WhiteOnMove(currentMove)) {
9310                 GameEnds(BlackWins, "Black mates", GE_FILE);
9311             } else {
9312                 GameEnds(WhiteWins, "White mates", GE_FILE);
9313             }
9314             break;
9315           case MT_STALEMATE:
9316             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9317             break;
9318         }
9319         done = TRUE;
9320         break;
9321
9322       case MoveNumberOne:
9323         if (lastLoadGameStart == GNUChessGame) {
9324             /* GNUChessGames have numbers, but they aren't move numbers */
9325             if (appData.debugMode)
9326               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9327                       yy_text, (int) moveType);
9328             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9329         }
9330         /* else fall thru */
9331
9332       case XBoardGame:
9333       case GNUChessGame:
9334       case PGNTag:
9335         /* Reached start of next game in file */
9336         if (appData.debugMode)
9337           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9338         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9339           case MT_NONE:
9340           case MT_CHECK:
9341             break;
9342           case MT_CHECKMATE:
9343           case MT_STAINMATE:
9344             if (WhiteOnMove(currentMove)) {
9345                 GameEnds(BlackWins, "Black mates", GE_FILE);
9346             } else {
9347                 GameEnds(WhiteWins, "White mates", GE_FILE);
9348             }
9349             break;
9350           case MT_STALEMATE:
9351             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9352             break;
9353         }
9354         done = TRUE;
9355         break;
9356
9357       case PositionDiagram:     /* should not happen; ignore */
9358       case ElapsedTime:         /* ignore */
9359       case NAG:                 /* ignore */
9360         if (appData.debugMode)
9361           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9362                   yy_text, (int) moveType);
9363         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9364
9365       case IllegalMove:
9366         if (appData.testLegality) {
9367             if (appData.debugMode)
9368               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9369             sprintf(move, _("Illegal move: %d.%s%s"),
9370                     (forwardMostMove / 2) + 1,
9371                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9372             DisplayError(move, 0);
9373             done = TRUE;
9374         } else {
9375             if (appData.debugMode)
9376               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9377                       yy_text, currentMoveString);
9378             fromX = currentMoveString[0] - AAA;
9379             fromY = currentMoveString[1] - ONE;
9380             toX = currentMoveString[2] - AAA;
9381             toY = currentMoveString[3] - ONE;
9382             promoChar = currentMoveString[4];
9383         }
9384         break;
9385
9386       case AmbiguousMove:
9387         if (appData.debugMode)
9388           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9389         sprintf(move, _("Ambiguous move: %d.%s%s"),
9390                 (forwardMostMove / 2) + 1,
9391                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9392         DisplayError(move, 0);
9393         done = TRUE;
9394         break;
9395
9396       default:
9397       case ImpossibleMove:
9398         if (appData.debugMode)
9399           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9400         sprintf(move, _("Illegal move: %d.%s%s"),
9401                 (forwardMostMove / 2) + 1,
9402                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9403         DisplayError(move, 0);
9404         done = TRUE;
9405         break;
9406     }
9407
9408     if (done) {
9409         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9410             DrawPosition(FALSE, boards[currentMove]);
9411             DisplayBothClocks();
9412             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9413               DisplayComment(currentMove - 1, commentList[currentMove]);
9414         }
9415         (void) StopLoadGameTimer();
9416         gameFileFP = NULL;
9417         cmailOldMove = forwardMostMove;
9418         return FALSE;
9419     } else {
9420         /* currentMoveString is set as a side-effect of yylex */
9421         strcat(currentMoveString, "\n");
9422         strcpy(moveList[forwardMostMove], currentMoveString);
9423         
9424         thinkOutput[0] = NULLCHAR;
9425         MakeMove(fromX, fromY, toX, toY, promoChar);
9426         currentMove = forwardMostMove;
9427         return TRUE;
9428     }
9429 }
9430
9431 /* Load the nth game from the given file */
9432 int
9433 LoadGameFromFile(filename, n, title, useList)
9434      char *filename;
9435      int n;
9436      char *title;
9437      /*Boolean*/ int useList;
9438 {
9439     FILE *f;
9440     char buf[MSG_SIZ];
9441
9442     if (strcmp(filename, "-") == 0) {
9443         f = stdin;
9444         title = "stdin";
9445     } else {
9446         f = fopen(filename, "rb");
9447         if (f == NULL) {
9448           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9449             DisplayError(buf, errno);
9450             return FALSE;
9451         }
9452     }
9453     if (fseek(f, 0, 0) == -1) {
9454         /* f is not seekable; probably a pipe */
9455         useList = FALSE;
9456     }
9457     if (useList && n == 0) {
9458         int error = GameListBuild(f);
9459         if (error) {
9460             DisplayError(_("Cannot build game list"), error);
9461         } else if (!ListEmpty(&gameList) &&
9462                    ((ListGame *) gameList.tailPred)->number > 1) {
9463             GameListPopUp(f, title);
9464             return TRUE;
9465         }
9466         GameListDestroy();
9467         n = 1;
9468     }
9469     if (n == 0) n = 1;
9470     return LoadGame(f, n, title, FALSE);
9471 }
9472
9473
9474 void
9475 MakeRegisteredMove()
9476 {
9477     int fromX, fromY, toX, toY;
9478     char promoChar;
9479     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9480         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9481           case CMAIL_MOVE:
9482           case CMAIL_DRAW:
9483             if (appData.debugMode)
9484               fprintf(debugFP, "Restoring %s for game %d\n",
9485                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9486     
9487             thinkOutput[0] = NULLCHAR;
9488             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9489             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9490             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9491             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9492             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9493             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9494             MakeMove(fromX, fromY, toX, toY, promoChar);
9495             ShowMove(fromX, fromY, toX, toY);
9496               
9497             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9498               case MT_NONE:
9499               case MT_CHECK:
9500                 break;
9501                 
9502               case MT_CHECKMATE:
9503               case MT_STAINMATE:
9504                 if (WhiteOnMove(currentMove)) {
9505                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9506                 } else {
9507                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9508                 }
9509                 break;
9510                 
9511               case MT_STALEMATE:
9512                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9513                 break;
9514             }
9515
9516             break;
9517             
9518           case CMAIL_RESIGN:
9519             if (WhiteOnMove(currentMove)) {
9520                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9521             } else {
9522                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9523             }
9524             break;
9525             
9526           case CMAIL_ACCEPT:
9527             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9528             break;
9529               
9530           default:
9531             break;
9532         }
9533     }
9534
9535     return;
9536 }
9537
9538 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9539 int
9540 CmailLoadGame(f, gameNumber, title, useList)
9541      FILE *f;
9542      int gameNumber;
9543      char *title;
9544      int useList;
9545 {
9546     int retVal;
9547
9548     if (gameNumber > nCmailGames) {
9549         DisplayError(_("No more games in this message"), 0);
9550         return FALSE;
9551     }
9552     if (f == lastLoadGameFP) {
9553         int offset = gameNumber - lastLoadGameNumber;
9554         if (offset == 0) {
9555             cmailMsg[0] = NULLCHAR;
9556             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9557                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9558                 nCmailMovesRegistered--;
9559             }
9560             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9561             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9562                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9563             }
9564         } else {
9565             if (! RegisterMove()) return FALSE;
9566         }
9567     }
9568
9569     retVal = LoadGame(f, gameNumber, title, useList);
9570
9571     /* Make move registered during previous look at this game, if any */
9572     MakeRegisteredMove();
9573
9574     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9575         commentList[currentMove]
9576           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9577         DisplayComment(currentMove - 1, commentList[currentMove]);
9578     }
9579
9580     return retVal;
9581 }
9582
9583 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9584 int
9585 ReloadGame(offset)
9586      int offset;
9587 {
9588     int gameNumber = lastLoadGameNumber + offset;
9589     if (lastLoadGameFP == NULL) {
9590         DisplayError(_("No game has been loaded yet"), 0);
9591         return FALSE;
9592     }
9593     if (gameNumber <= 0) {
9594         DisplayError(_("Can't back up any further"), 0);
9595         return FALSE;
9596     }
9597     if (cmailMsgLoaded) {
9598         return CmailLoadGame(lastLoadGameFP, gameNumber,
9599                              lastLoadGameTitle, lastLoadGameUseList);
9600     } else {
9601         return LoadGame(lastLoadGameFP, gameNumber,
9602                         lastLoadGameTitle, lastLoadGameUseList);
9603     }
9604 }
9605
9606
9607
9608 /* Load the nth game from open file f */
9609 int
9610 LoadGame(f, gameNumber, title, useList)
9611      FILE *f;
9612      int gameNumber;
9613      char *title;
9614      int useList;
9615 {
9616     ChessMove cm;
9617     char buf[MSG_SIZ];
9618     int gn = gameNumber;
9619     ListGame *lg = NULL;
9620     int numPGNTags = 0;
9621     int err;
9622     GameMode oldGameMode;
9623     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9624
9625     if (appData.debugMode) 
9626         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9627
9628     if (gameMode == Training )
9629         SetTrainingModeOff();
9630
9631     oldGameMode = gameMode;
9632     if (gameMode != BeginningOfGame) {
9633       Reset(FALSE, TRUE);
9634     }
9635
9636     gameFileFP = f;
9637     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9638         fclose(lastLoadGameFP);
9639     }
9640
9641     if (useList) {
9642         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9643         
9644         if (lg) {
9645             fseek(f, lg->offset, 0);
9646             GameListHighlight(gameNumber);
9647             gn = 1;
9648         }
9649         else {
9650             DisplayError(_("Game number out of range"), 0);
9651             return FALSE;
9652         }
9653     } else {
9654         GameListDestroy();
9655         if (fseek(f, 0, 0) == -1) {
9656             if (f == lastLoadGameFP ?
9657                 gameNumber == lastLoadGameNumber + 1 :
9658                 gameNumber == 1) {
9659                 gn = 1;
9660             } else {
9661                 DisplayError(_("Can't seek on game file"), 0);
9662                 return FALSE;
9663             }
9664         }
9665     }
9666     lastLoadGameFP = f;
9667     lastLoadGameNumber = gameNumber;
9668     strcpy(lastLoadGameTitle, title);
9669     lastLoadGameUseList = useList;
9670
9671     yynewfile(f);
9672
9673     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9674       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9675                 lg->gameInfo.black);
9676             DisplayTitle(buf);
9677     } else if (*title != NULLCHAR) {
9678         if (gameNumber > 1) {
9679             sprintf(buf, "%s %d", title, gameNumber);
9680             DisplayTitle(buf);
9681         } else {
9682             DisplayTitle(title);
9683         }
9684     }
9685
9686     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9687         gameMode = PlayFromGameFile;
9688         ModeHighlight();
9689     }
9690
9691     currentMove = forwardMostMove = backwardMostMove = 0;
9692     CopyBoard(boards[0], initialPosition);
9693     StopClocks();
9694
9695     /*
9696      * Skip the first gn-1 games in the file.
9697      * Also skip over anything that precedes an identifiable 
9698      * start of game marker, to avoid being confused by 
9699      * garbage at the start of the file.  Currently 
9700      * recognized start of game markers are the move number "1",
9701      * the pattern "gnuchess .* game", the pattern
9702      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9703      * A game that starts with one of the latter two patterns
9704      * will also have a move number 1, possibly
9705      * following a position diagram.
9706      * 5-4-02: Let's try being more lenient and allowing a game to
9707      * start with an unnumbered move.  Does that break anything?
9708      */
9709     cm = lastLoadGameStart = (ChessMove) 0;
9710     while (gn > 0) {
9711         yyboardindex = forwardMostMove;
9712         cm = (ChessMove) yylex();
9713         switch (cm) {
9714           case (ChessMove) 0:
9715             if (cmailMsgLoaded) {
9716                 nCmailGames = CMAIL_MAX_GAMES - gn;
9717             } else {
9718                 Reset(TRUE, TRUE);
9719                 DisplayError(_("Game not found in file"), 0);
9720             }
9721             return FALSE;
9722
9723           case GNUChessGame:
9724           case XBoardGame:
9725             gn--;
9726             lastLoadGameStart = cm;
9727             break;
9728             
9729           case MoveNumberOne:
9730             switch (lastLoadGameStart) {
9731               case GNUChessGame:
9732               case XBoardGame:
9733               case PGNTag:
9734                 break;
9735               case MoveNumberOne:
9736               case (ChessMove) 0:
9737                 gn--;           /* count this game */
9738                 lastLoadGameStart = cm;
9739                 break;
9740               default:
9741                 /* impossible */
9742                 break;
9743             }
9744             break;
9745
9746           case PGNTag:
9747             switch (lastLoadGameStart) {
9748               case GNUChessGame:
9749               case PGNTag:
9750               case MoveNumberOne:
9751               case (ChessMove) 0:
9752                 gn--;           /* count this game */
9753                 lastLoadGameStart = cm;
9754                 break;
9755               case XBoardGame:
9756                 lastLoadGameStart = cm; /* game counted already */
9757                 break;
9758               default:
9759                 /* impossible */
9760                 break;
9761             }
9762             if (gn > 0) {
9763                 do {
9764                     yyboardindex = forwardMostMove;
9765                     cm = (ChessMove) yylex();
9766                 } while (cm == PGNTag || cm == Comment);
9767             }
9768             break;
9769
9770           case WhiteWins:
9771           case BlackWins:
9772           case GameIsDrawn:
9773             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9774                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9775                     != CMAIL_OLD_RESULT) {
9776                     nCmailResults ++ ;
9777                     cmailResult[  CMAIL_MAX_GAMES
9778                                 - gn - 1] = CMAIL_OLD_RESULT;
9779                 }
9780             }
9781             break;
9782
9783           case NormalMove:
9784             /* Only a NormalMove can be at the start of a game
9785              * without a position diagram. */
9786             if (lastLoadGameStart == (ChessMove) 0) {
9787               gn--;
9788               lastLoadGameStart = MoveNumberOne;
9789             }
9790             break;
9791
9792           default:
9793             break;
9794         }
9795     }
9796     
9797     if (appData.debugMode)
9798       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9799
9800     if (cm == XBoardGame) {
9801         /* Skip any header junk before position diagram and/or move 1 */
9802         for (;;) {
9803             yyboardindex = forwardMostMove;
9804             cm = (ChessMove) yylex();
9805
9806             if (cm == (ChessMove) 0 ||
9807                 cm == GNUChessGame || cm == XBoardGame) {
9808                 /* Empty game; pretend end-of-file and handle later */
9809                 cm = (ChessMove) 0;
9810                 break;
9811             }
9812
9813             if (cm == MoveNumberOne || cm == PositionDiagram ||
9814                 cm == PGNTag || cm == Comment)
9815               break;
9816         }
9817     } else if (cm == GNUChessGame) {
9818         if (gameInfo.event != NULL) {
9819             free(gameInfo.event);
9820         }
9821         gameInfo.event = StrSave(yy_text);
9822     }   
9823
9824     startedFromSetupPosition = FALSE;
9825     while (cm == PGNTag) {
9826         if (appData.debugMode) 
9827           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9828         err = ParsePGNTag(yy_text, &gameInfo);
9829         if (!err) numPGNTags++;
9830
9831         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9832         if(gameInfo.variant != oldVariant) {
9833             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9834             InitPosition(TRUE);
9835             oldVariant = gameInfo.variant;
9836             if (appData.debugMode) 
9837               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9838         }
9839
9840
9841         if (gameInfo.fen != NULL) {
9842           Board initial_position;
9843           startedFromSetupPosition = TRUE;
9844           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9845             Reset(TRUE, TRUE);
9846             DisplayError(_("Bad FEN position in file"), 0);
9847             return FALSE;
9848           }
9849           CopyBoard(boards[0], initial_position);
9850           if (blackPlaysFirst) {
9851             currentMove = forwardMostMove = backwardMostMove = 1;
9852             CopyBoard(boards[1], initial_position);
9853             strcpy(moveList[0], "");
9854             strcpy(parseList[0], "");
9855             timeRemaining[0][1] = whiteTimeRemaining;
9856             timeRemaining[1][1] = blackTimeRemaining;
9857             if (commentList[0] != NULL) {
9858               commentList[1] = commentList[0];
9859               commentList[0] = NULL;
9860             }
9861           } else {
9862             currentMove = forwardMostMove = backwardMostMove = 0;
9863           }
9864           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9865           {   int i;
9866               initialRulePlies = FENrulePlies;
9867               for( i=0; i< nrCastlingRights; i++ )
9868                   initialRights[i] = initial_position[CASTLING][i];
9869           }
9870           yyboardindex = forwardMostMove;
9871           free(gameInfo.fen);
9872           gameInfo.fen = NULL;
9873         }
9874
9875         yyboardindex = forwardMostMove;
9876         cm = (ChessMove) yylex();
9877
9878         /* Handle comments interspersed among the tags */
9879         while (cm == Comment) {
9880             char *p;
9881             if (appData.debugMode) 
9882               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9883             p = yy_text;
9884             AppendComment(currentMove, p, FALSE);
9885             yyboardindex = forwardMostMove;
9886             cm = (ChessMove) yylex();
9887         }
9888     }
9889
9890     /* don't rely on existence of Event tag since if game was
9891      * pasted from clipboard the Event tag may not exist
9892      */
9893     if (numPGNTags > 0){
9894         char *tags;
9895         if (gameInfo.variant == VariantNormal) {
9896           gameInfo.variant = StringToVariant(gameInfo.event);
9897         }
9898         if (!matchMode) {
9899           if( appData.autoDisplayTags ) {
9900             tags = PGNTags(&gameInfo);
9901             TagsPopUp(tags, CmailMsg());
9902             free(tags);
9903           }
9904         }
9905     } else {
9906         /* Make something up, but don't display it now */
9907         SetGameInfo();
9908         TagsPopDown();
9909     }
9910
9911     if (cm == PositionDiagram) {
9912         int i, j;
9913         char *p;
9914         Board initial_position;
9915
9916         if (appData.debugMode)
9917           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9918
9919         if (!startedFromSetupPosition) {
9920             p = yy_text;
9921             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9922               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9923                 switch (*p) {
9924                   case '[':
9925                   case '-':
9926                   case ' ':
9927                   case '\t':
9928                   case '\n':
9929                   case '\r':
9930                     break;
9931                   default:
9932                     initial_position[i][j++] = CharToPiece(*p);
9933                     break;
9934                 }
9935             while (*p == ' ' || *p == '\t' ||
9936                    *p == '\n' || *p == '\r') p++;
9937         
9938             if (strncmp(p, "black", strlen("black"))==0)
9939               blackPlaysFirst = TRUE;
9940             else
9941               blackPlaysFirst = FALSE;
9942             startedFromSetupPosition = TRUE;
9943         
9944             CopyBoard(boards[0], initial_position);
9945             if (blackPlaysFirst) {
9946                 currentMove = forwardMostMove = backwardMostMove = 1;
9947                 CopyBoard(boards[1], initial_position);
9948                 strcpy(moveList[0], "");
9949                 strcpy(parseList[0], "");
9950                 timeRemaining[0][1] = whiteTimeRemaining;
9951                 timeRemaining[1][1] = blackTimeRemaining;
9952                 if (commentList[0] != NULL) {
9953                     commentList[1] = commentList[0];
9954                     commentList[0] = NULL;
9955                 }
9956             } else {
9957                 currentMove = forwardMostMove = backwardMostMove = 0;
9958             }
9959         }
9960         yyboardindex = forwardMostMove;
9961         cm = (ChessMove) yylex();
9962     }
9963
9964     if (first.pr == NoProc) {
9965         StartChessProgram(&first);
9966     }
9967     InitChessProgram(&first, FALSE);
9968     SendToProgram("force\n", &first);
9969     if (startedFromSetupPosition) {
9970         SendBoard(&first, forwardMostMove);
9971     if (appData.debugMode) {
9972         fprintf(debugFP, "Load Game\n");
9973     }
9974         DisplayBothClocks();
9975     }      
9976
9977     /* [HGM] server: flag to write setup moves in broadcast file as one */
9978     loadFlag = appData.suppressLoadMoves;
9979
9980     while (cm == Comment) {
9981         char *p;
9982         if (appData.debugMode) 
9983           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9984         p = yy_text;
9985         AppendComment(currentMove, p, FALSE);
9986         yyboardindex = forwardMostMove;
9987         cm = (ChessMove) yylex();
9988     }
9989
9990     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9991         cm == WhiteWins || cm == BlackWins ||
9992         cm == GameIsDrawn || cm == GameUnfinished) {
9993         DisplayMessage("", _("No moves in game"));
9994         if (cmailMsgLoaded) {
9995             if (appData.debugMode)
9996               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9997             ClearHighlights();
9998             flipView = FALSE;
9999         }
10000         DrawPosition(FALSE, boards[currentMove]);
10001         DisplayBothClocks();
10002         gameMode = EditGame;
10003         ModeHighlight();
10004         gameFileFP = NULL;
10005         cmailOldMove = 0;
10006         return TRUE;
10007     }
10008
10009     // [HGM] PV info: routine tests if comment empty
10010     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10011         DisplayComment(currentMove - 1, commentList[currentMove]);
10012     }
10013     if (!matchMode && appData.timeDelay != 0) 
10014       DrawPosition(FALSE, boards[currentMove]);
10015
10016     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10017       programStats.ok_to_send = 1;
10018     }
10019
10020     /* if the first token after the PGN tags is a move
10021      * and not move number 1, retrieve it from the parser 
10022      */
10023     if (cm != MoveNumberOne)
10024         LoadGameOneMove(cm);
10025
10026     /* load the remaining moves from the file */
10027     while (LoadGameOneMove((ChessMove)0)) {
10028       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10029       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10030     }
10031
10032     /* rewind to the start of the game */
10033     currentMove = backwardMostMove;
10034
10035     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10036
10037     if (oldGameMode == AnalyzeFile ||
10038         oldGameMode == AnalyzeMode) {
10039       AnalyzeFileEvent();
10040     }
10041
10042     if (matchMode || appData.timeDelay == 0) {
10043       ToEndEvent();
10044       gameMode = EditGame;
10045       ModeHighlight();
10046     } else if (appData.timeDelay > 0) {
10047       AutoPlayGameLoop();
10048     }
10049
10050     if (appData.debugMode) 
10051         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10052
10053     loadFlag = 0; /* [HGM] true game starts */
10054     return TRUE;
10055 }
10056
10057 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10058 int
10059 ReloadPosition(offset)
10060      int offset;
10061 {
10062     int positionNumber = lastLoadPositionNumber + offset;
10063     if (lastLoadPositionFP == NULL) {
10064         DisplayError(_("No position has been loaded yet"), 0);
10065         return FALSE;
10066     }
10067     if (positionNumber <= 0) {
10068         DisplayError(_("Can't back up any further"), 0);
10069         return FALSE;
10070     }
10071     return LoadPosition(lastLoadPositionFP, positionNumber,
10072                         lastLoadPositionTitle);
10073 }
10074
10075 /* Load the nth position from the given file */
10076 int
10077 LoadPositionFromFile(filename, n, title)
10078      char *filename;
10079      int n;
10080      char *title;
10081 {
10082     FILE *f;
10083     char buf[MSG_SIZ];
10084
10085     if (strcmp(filename, "-") == 0) {
10086         return LoadPosition(stdin, n, "stdin");
10087     } else {
10088         f = fopen(filename, "rb");
10089         if (f == NULL) {
10090             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10091             DisplayError(buf, errno);
10092             return FALSE;
10093         } else {
10094             return LoadPosition(f, n, title);
10095         }
10096     }
10097 }
10098
10099 /* Load the nth position from the given open file, and close it */
10100 int
10101 LoadPosition(f, positionNumber, title)
10102      FILE *f;
10103      int positionNumber;
10104      char *title;
10105 {
10106     char *p, line[MSG_SIZ];
10107     Board initial_position;
10108     int i, j, fenMode, pn;
10109     
10110     if (gameMode == Training )
10111         SetTrainingModeOff();
10112
10113     if (gameMode != BeginningOfGame) {
10114         Reset(FALSE, TRUE);
10115     }
10116     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10117         fclose(lastLoadPositionFP);
10118     }
10119     if (positionNumber == 0) positionNumber = 1;
10120     lastLoadPositionFP = f;
10121     lastLoadPositionNumber = positionNumber;
10122     strcpy(lastLoadPositionTitle, title);
10123     if (first.pr == NoProc) {
10124       StartChessProgram(&first);
10125       InitChessProgram(&first, FALSE);
10126     }    
10127     pn = positionNumber;
10128     if (positionNumber < 0) {
10129         /* Negative position number means to seek to that byte offset */
10130         if (fseek(f, -positionNumber, 0) == -1) {
10131             DisplayError(_("Can't seek on position file"), 0);
10132             return FALSE;
10133         };
10134         pn = 1;
10135     } else {
10136         if (fseek(f, 0, 0) == -1) {
10137             if (f == lastLoadPositionFP ?
10138                 positionNumber == lastLoadPositionNumber + 1 :
10139                 positionNumber == 1) {
10140                 pn = 1;
10141             } else {
10142                 DisplayError(_("Can't seek on position file"), 0);
10143                 return FALSE;
10144             }
10145         }
10146     }
10147     /* See if this file is FEN or old-style xboard */
10148     if (fgets(line, MSG_SIZ, f) == NULL) {
10149         DisplayError(_("Position not found in file"), 0);
10150         return FALSE;
10151     }
10152     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10153     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10154
10155     if (pn >= 2) {
10156         if (fenMode || line[0] == '#') pn--;
10157         while (pn > 0) {
10158             /* skip positions before number pn */
10159             if (fgets(line, MSG_SIZ, f) == NULL) {
10160                 Reset(TRUE, TRUE);
10161                 DisplayError(_("Position not found in file"), 0);
10162                 return FALSE;
10163             }
10164             if (fenMode || line[0] == '#') pn--;
10165         }
10166     }
10167
10168     if (fenMode) {
10169         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10170             DisplayError(_("Bad FEN position in file"), 0);
10171             return FALSE;
10172         }
10173     } else {
10174         (void) fgets(line, MSG_SIZ, f);
10175         (void) fgets(line, MSG_SIZ, f);
10176     
10177         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10178             (void) fgets(line, MSG_SIZ, f);
10179             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10180                 if (*p == ' ')
10181                   continue;
10182                 initial_position[i][j++] = CharToPiece(*p);
10183             }
10184         }
10185     
10186         blackPlaysFirst = FALSE;
10187         if (!feof(f)) {
10188             (void) fgets(line, MSG_SIZ, f);
10189             if (strncmp(line, "black", strlen("black"))==0)
10190               blackPlaysFirst = TRUE;
10191         }
10192     }
10193     startedFromSetupPosition = TRUE;
10194     
10195     SendToProgram("force\n", &first);
10196     CopyBoard(boards[0], initial_position);
10197     if (blackPlaysFirst) {
10198         currentMove = forwardMostMove = backwardMostMove = 1;
10199         strcpy(moveList[0], "");
10200         strcpy(parseList[0], "");
10201         CopyBoard(boards[1], initial_position);
10202         DisplayMessage("", _("Black to play"));
10203     } else {
10204         currentMove = forwardMostMove = backwardMostMove = 0;
10205         DisplayMessage("", _("White to play"));
10206     }
10207     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10208     SendBoard(&first, forwardMostMove);
10209     if (appData.debugMode) {
10210 int i, j;
10211   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10212   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10213         fprintf(debugFP, "Load Position\n");
10214     }
10215
10216     if (positionNumber > 1) {
10217         sprintf(line, "%s %d", title, positionNumber);
10218         DisplayTitle(line);
10219     } else {
10220         DisplayTitle(title);
10221     }
10222     gameMode = EditGame;
10223     ModeHighlight();
10224     ResetClocks();
10225     timeRemaining[0][1] = whiteTimeRemaining;
10226     timeRemaining[1][1] = blackTimeRemaining;
10227     DrawPosition(FALSE, boards[currentMove]);
10228    
10229     return TRUE;
10230 }
10231
10232
10233 void
10234 CopyPlayerNameIntoFileName(dest, src)
10235      char **dest, *src;
10236 {
10237     while (*src != NULLCHAR && *src != ',') {
10238         if (*src == ' ') {
10239             *(*dest)++ = '_';
10240             src++;
10241         } else {
10242             *(*dest)++ = *src++;
10243         }
10244     }
10245 }
10246
10247 char *DefaultFileName(ext)
10248      char *ext;
10249 {
10250     static char def[MSG_SIZ];
10251     char *p;
10252
10253     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10254         p = def;
10255         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10256         *p++ = '-';
10257         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10258         *p++ = '.';
10259         strcpy(p, ext);
10260     } else {
10261         def[0] = NULLCHAR;
10262     }
10263     return def;
10264 }
10265
10266 /* Save the current game to the given file */
10267 int
10268 SaveGameToFile(filename, append)
10269      char *filename;
10270      int append;
10271 {
10272     FILE *f;
10273     char buf[MSG_SIZ];
10274
10275     if (strcmp(filename, "-") == 0) {
10276         return SaveGame(stdout, 0, NULL);
10277     } else {
10278         f = fopen(filename, append ? "a" : "w");
10279         if (f == NULL) {
10280             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10281             DisplayError(buf, errno);
10282             return FALSE;
10283         } else {
10284             return SaveGame(f, 0, NULL);
10285         }
10286     }
10287 }
10288
10289 char *
10290 SavePart(str)
10291      char *str;
10292 {
10293     static char buf[MSG_SIZ];
10294     char *p;
10295     
10296     p = strchr(str, ' ');
10297     if (p == NULL) return str;
10298     strncpy(buf, str, p - str);
10299     buf[p - str] = NULLCHAR;
10300     return buf;
10301 }
10302
10303 #define PGN_MAX_LINE 75
10304
10305 #define PGN_SIDE_WHITE  0
10306 #define PGN_SIDE_BLACK  1
10307
10308 /* [AS] */
10309 static int FindFirstMoveOutOfBook( int side )
10310 {
10311     int result = -1;
10312
10313     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10314         int index = backwardMostMove;
10315         int has_book_hit = 0;
10316
10317         if( (index % 2) != side ) {
10318             index++;
10319         }
10320
10321         while( index < forwardMostMove ) {
10322             /* Check to see if engine is in book */
10323             int depth = pvInfoList[index].depth;
10324             int score = pvInfoList[index].score;
10325             int in_book = 0;
10326
10327             if( depth <= 2 ) {
10328                 in_book = 1;
10329             }
10330             else if( score == 0 && depth == 63 ) {
10331                 in_book = 1; /* Zappa */
10332             }
10333             else if( score == 2 && depth == 99 ) {
10334                 in_book = 1; /* Abrok */
10335             }
10336
10337             has_book_hit += in_book;
10338
10339             if( ! in_book ) {
10340                 result = index;
10341
10342                 break;
10343             }
10344
10345             index += 2;
10346         }
10347     }
10348
10349     return result;
10350 }
10351
10352 /* [AS] */
10353 void GetOutOfBookInfo( char * buf )
10354 {
10355     int oob[2];
10356     int i;
10357     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10358
10359     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10360     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10361
10362     *buf = '\0';
10363
10364     if( oob[0] >= 0 || oob[1] >= 0 ) {
10365         for( i=0; i<2; i++ ) {
10366             int idx = oob[i];
10367
10368             if( idx >= 0 ) {
10369                 if( i > 0 && oob[0] >= 0 ) {
10370                     strcat( buf, "   " );
10371                 }
10372
10373                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10374                 sprintf( buf+strlen(buf), "%s%.2f", 
10375                     pvInfoList[idx].score >= 0 ? "+" : "",
10376                     pvInfoList[idx].score / 100.0 );
10377             }
10378         }
10379     }
10380 }
10381
10382 /* Save game in PGN style and close the file */
10383 int
10384 SaveGamePGN(f)
10385      FILE *f;
10386 {
10387     int i, offset, linelen, newblock;
10388     time_t tm;
10389 //    char *movetext;
10390     char numtext[32];
10391     int movelen, numlen, blank;
10392     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10393
10394     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10395     
10396     tm = time((time_t *) NULL);
10397     
10398     PrintPGNTags(f, &gameInfo);
10399     
10400     if (backwardMostMove > 0 || startedFromSetupPosition) {
10401         char *fen = PositionToFEN(backwardMostMove, NULL);
10402         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10403         fprintf(f, "\n{--------------\n");
10404         PrintPosition(f, backwardMostMove);
10405         fprintf(f, "--------------}\n");
10406         free(fen);
10407     }
10408     else {
10409         /* [AS] Out of book annotation */
10410         if( appData.saveOutOfBookInfo ) {
10411             char buf[64];
10412
10413             GetOutOfBookInfo( buf );
10414
10415             if( buf[0] != '\0' ) {
10416                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10417             }
10418         }
10419
10420         fprintf(f, "\n");
10421     }
10422
10423     i = backwardMostMove;
10424     linelen = 0;
10425     newblock = TRUE;
10426
10427     while (i < forwardMostMove) {
10428         /* Print comments preceding this move */
10429         if (commentList[i] != NULL) {
10430             if (linelen > 0) fprintf(f, "\n");
10431             fprintf(f, "%s", commentList[i]);
10432             linelen = 0;
10433             newblock = TRUE;
10434         }
10435
10436         /* Format move number */
10437         if ((i % 2) == 0) {
10438             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10439         } else {
10440             if (newblock) {
10441                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10442             } else {
10443                 numtext[0] = NULLCHAR;
10444             }
10445         }
10446         numlen = strlen(numtext);
10447         newblock = FALSE;
10448
10449         /* Print move number */
10450         blank = linelen > 0 && numlen > 0;
10451         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10452             fprintf(f, "\n");
10453             linelen = 0;
10454             blank = 0;
10455         }
10456         if (blank) {
10457             fprintf(f, " ");
10458             linelen++;
10459         }
10460         fprintf(f, "%s", numtext);
10461         linelen += numlen;
10462
10463         /* Get move */
10464         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10465         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10466
10467         /* Print move */
10468         blank = linelen > 0 && movelen > 0;
10469         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10470             fprintf(f, "\n");
10471             linelen = 0;
10472             blank = 0;
10473         }
10474         if (blank) {
10475             fprintf(f, " ");
10476             linelen++;
10477         }
10478         fprintf(f, "%s", move_buffer);
10479         linelen += movelen;
10480
10481         /* [AS] Add PV info if present */
10482         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10483             /* [HGM] add time */
10484             char buf[MSG_SIZ]; int seconds;
10485
10486             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10487
10488             if( seconds <= 0) buf[0] = 0; else
10489             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10490                 seconds = (seconds + 4)/10; // round to full seconds
10491                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10492                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10493             }
10494
10495             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10496                 pvInfoList[i].score >= 0 ? "+" : "",
10497                 pvInfoList[i].score / 100.0,
10498                 pvInfoList[i].depth,
10499                 buf );
10500
10501             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10502
10503             /* Print score/depth */
10504             blank = linelen > 0 && movelen > 0;
10505             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10506                 fprintf(f, "\n");
10507                 linelen = 0;
10508                 blank = 0;
10509             }
10510             if (blank) {
10511                 fprintf(f, " ");
10512                 linelen++;
10513             }
10514             fprintf(f, "%s", move_buffer);
10515             linelen += movelen;
10516         }
10517
10518         i++;
10519     }
10520     
10521     /* Start a new line */
10522     if (linelen > 0) fprintf(f, "\n");
10523
10524     /* Print comments after last move */
10525     if (commentList[i] != NULL) {
10526         fprintf(f, "%s\n", commentList[i]);
10527     }
10528
10529     /* Print result */
10530     if (gameInfo.resultDetails != NULL &&
10531         gameInfo.resultDetails[0] != NULLCHAR) {
10532         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10533                 PGNResult(gameInfo.result));
10534     } else {
10535         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10536     }
10537
10538     fclose(f);
10539     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10540     return TRUE;
10541 }
10542
10543 /* Save game in old style and close the file */
10544 int
10545 SaveGameOldStyle(f)
10546      FILE *f;
10547 {
10548     int i, offset;
10549     time_t tm;
10550     
10551     tm = time((time_t *) NULL);
10552     
10553     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10554     PrintOpponents(f);
10555     
10556     if (backwardMostMove > 0 || startedFromSetupPosition) {
10557         fprintf(f, "\n[--------------\n");
10558         PrintPosition(f, backwardMostMove);
10559         fprintf(f, "--------------]\n");
10560     } else {
10561         fprintf(f, "\n");
10562     }
10563
10564     i = backwardMostMove;
10565     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10566
10567     while (i < forwardMostMove) {
10568         if (commentList[i] != NULL) {
10569             fprintf(f, "[%s]\n", commentList[i]);
10570         }
10571
10572         if ((i % 2) == 1) {
10573             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10574             i++;
10575         } else {
10576             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10577             i++;
10578             if (commentList[i] != NULL) {
10579                 fprintf(f, "\n");
10580                 continue;
10581             }
10582             if (i >= forwardMostMove) {
10583                 fprintf(f, "\n");
10584                 break;
10585             }
10586             fprintf(f, "%s\n", parseList[i]);
10587             i++;
10588         }
10589     }
10590     
10591     if (commentList[i] != NULL) {
10592         fprintf(f, "[%s]\n", commentList[i]);
10593     }
10594
10595     /* This isn't really the old style, but it's close enough */
10596     if (gameInfo.resultDetails != NULL &&
10597         gameInfo.resultDetails[0] != NULLCHAR) {
10598         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10599                 gameInfo.resultDetails);
10600     } else {
10601         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10602     }
10603
10604     fclose(f);
10605     return TRUE;
10606 }
10607
10608 /* Save the current game to open file f and close the file */
10609 int
10610 SaveGame(f, dummy, dummy2)
10611      FILE *f;
10612      int dummy;
10613      char *dummy2;
10614 {
10615     if (gameMode == EditPosition) EditPositionDone(TRUE);
10616     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10617     if (appData.oldSaveStyle)
10618       return SaveGameOldStyle(f);
10619     else
10620       return SaveGamePGN(f);
10621 }
10622
10623 /* Save the current position to the given file */
10624 int
10625 SavePositionToFile(filename)
10626      char *filename;
10627 {
10628     FILE *f;
10629     char buf[MSG_SIZ];
10630
10631     if (strcmp(filename, "-") == 0) {
10632         return SavePosition(stdout, 0, NULL);
10633     } else {
10634         f = fopen(filename, "a");
10635         if (f == NULL) {
10636             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10637             DisplayError(buf, errno);
10638             return FALSE;
10639         } else {
10640             SavePosition(f, 0, NULL);
10641             return TRUE;
10642         }
10643     }
10644 }
10645
10646 /* Save the current position to the given open file and close the file */
10647 int
10648 SavePosition(f, dummy, dummy2)
10649      FILE *f;
10650      int dummy;
10651      char *dummy2;
10652 {
10653     time_t tm;
10654     char *fen;
10655     
10656     if (gameMode == EditPosition) EditPositionDone(TRUE);
10657     if (appData.oldSaveStyle) {
10658         tm = time((time_t *) NULL);
10659     
10660         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10661         PrintOpponents(f);
10662         fprintf(f, "[--------------\n");
10663         PrintPosition(f, currentMove);
10664         fprintf(f, "--------------]\n");
10665     } else {
10666         fen = PositionToFEN(currentMove, NULL);
10667         fprintf(f, "%s\n", fen);
10668         free(fen);
10669     }
10670     fclose(f);
10671     return TRUE;
10672 }
10673
10674 void
10675 ReloadCmailMsgEvent(unregister)
10676      int unregister;
10677 {
10678 #if !WIN32
10679     static char *inFilename = NULL;
10680     static char *outFilename;
10681     int i;
10682     struct stat inbuf, outbuf;
10683     int status;
10684     
10685     /* Any registered moves are unregistered if unregister is set, */
10686     /* i.e. invoked by the signal handler */
10687     if (unregister) {
10688         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10689             cmailMoveRegistered[i] = FALSE;
10690             if (cmailCommentList[i] != NULL) {
10691                 free(cmailCommentList[i]);
10692                 cmailCommentList[i] = NULL;
10693             }
10694         }
10695         nCmailMovesRegistered = 0;
10696     }
10697
10698     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10699         cmailResult[i] = CMAIL_NOT_RESULT;
10700     }
10701     nCmailResults = 0;
10702
10703     if (inFilename == NULL) {
10704         /* Because the filenames are static they only get malloced once  */
10705         /* and they never get freed                                      */
10706         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10707         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10708
10709         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10710         sprintf(outFilename, "%s.out", appData.cmailGameName);
10711     }
10712     
10713     status = stat(outFilename, &outbuf);
10714     if (status < 0) {
10715         cmailMailedMove = FALSE;
10716     } else {
10717         status = stat(inFilename, &inbuf);
10718         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10719     }
10720     
10721     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10722        counts the games, notes how each one terminated, etc.
10723        
10724        It would be nice to remove this kludge and instead gather all
10725        the information while building the game list.  (And to keep it
10726        in the game list nodes instead of having a bunch of fixed-size
10727        parallel arrays.)  Note this will require getting each game's
10728        termination from the PGN tags, as the game list builder does
10729        not process the game moves.  --mann
10730        */
10731     cmailMsgLoaded = TRUE;
10732     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10733     
10734     /* Load first game in the file or popup game menu */
10735     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10736
10737 #endif /* !WIN32 */
10738     return;
10739 }
10740
10741 int
10742 RegisterMove()
10743 {
10744     FILE *f;
10745     char string[MSG_SIZ];
10746
10747     if (   cmailMailedMove
10748         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10749         return TRUE;            /* Allow free viewing  */
10750     }
10751
10752     /* Unregister move to ensure that we don't leave RegisterMove        */
10753     /* with the move registered when the conditions for registering no   */
10754     /* longer hold                                                       */
10755     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10756         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10757         nCmailMovesRegistered --;
10758
10759         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10760           {
10761               free(cmailCommentList[lastLoadGameNumber - 1]);
10762               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10763           }
10764     }
10765
10766     if (cmailOldMove == -1) {
10767         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10768         return FALSE;
10769     }
10770
10771     if (currentMove > cmailOldMove + 1) {
10772         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10773         return FALSE;
10774     }
10775
10776     if (currentMove < cmailOldMove) {
10777         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10778         return FALSE;
10779     }
10780
10781     if (forwardMostMove > currentMove) {
10782         /* Silently truncate extra moves */
10783         TruncateGame();
10784     }
10785
10786     if (   (currentMove == cmailOldMove + 1)
10787         || (   (currentMove == cmailOldMove)
10788             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10789                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10790         if (gameInfo.result != GameUnfinished) {
10791             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10792         }
10793
10794         if (commentList[currentMove] != NULL) {
10795             cmailCommentList[lastLoadGameNumber - 1]
10796               = StrSave(commentList[currentMove]);
10797         }
10798         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10799
10800         if (appData.debugMode)
10801           fprintf(debugFP, "Saving %s for game %d\n",
10802                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10803
10804         sprintf(string,
10805                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10806         
10807         f = fopen(string, "w");
10808         if (appData.oldSaveStyle) {
10809             SaveGameOldStyle(f); /* also closes the file */
10810             
10811             sprintf(string, "%s.pos.out", appData.cmailGameName);
10812             f = fopen(string, "w");
10813             SavePosition(f, 0, NULL); /* also closes the file */
10814         } else {
10815             fprintf(f, "{--------------\n");
10816             PrintPosition(f, currentMove);
10817             fprintf(f, "--------------}\n\n");
10818             
10819             SaveGame(f, 0, NULL); /* also closes the file*/
10820         }
10821         
10822         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10823         nCmailMovesRegistered ++;
10824     } else if (nCmailGames == 1) {
10825         DisplayError(_("You have not made a move yet"), 0);
10826         return FALSE;
10827     }
10828
10829     return TRUE;
10830 }
10831
10832 void
10833 MailMoveEvent()
10834 {
10835 #if !WIN32
10836     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10837     FILE *commandOutput;
10838     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10839     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10840     int nBuffers;
10841     int i;
10842     int archived;
10843     char *arcDir;
10844
10845     if (! cmailMsgLoaded) {
10846         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10847         return;
10848     }
10849
10850     if (nCmailGames == nCmailResults) {
10851         DisplayError(_("No unfinished games"), 0);
10852         return;
10853     }
10854
10855 #if CMAIL_PROHIBIT_REMAIL
10856     if (cmailMailedMove) {
10857         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);
10858         DisplayError(msg, 0);
10859         return;
10860     }
10861 #endif
10862
10863     if (! (cmailMailedMove || RegisterMove())) return;
10864     
10865     if (   cmailMailedMove
10866         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10867         sprintf(string, partCommandString,
10868                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10869         commandOutput = popen(string, "r");
10870
10871         if (commandOutput == NULL) {
10872             DisplayError(_("Failed to invoke cmail"), 0);
10873         } else {
10874             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10875                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10876             }
10877             if (nBuffers > 1) {
10878                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10879                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10880                 nBytes = MSG_SIZ - 1;
10881             } else {
10882                 (void) memcpy(msg, buffer, nBytes);
10883             }
10884             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10885
10886             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10887                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
10888
10889                 archived = TRUE;
10890                 for (i = 0; i < nCmailGames; i ++) {
10891                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
10892                         archived = FALSE;
10893                     }
10894                 }
10895                 if (   archived
10896                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10897                         != NULL)) {
10898                     sprintf(buffer, "%s/%s.%s.archive",
10899                             arcDir,
10900                             appData.cmailGameName,
10901                             gameInfo.date);
10902                     LoadGameFromFile(buffer, 1, buffer, FALSE);
10903                     cmailMsgLoaded = FALSE;
10904                 }
10905             }
10906
10907             DisplayInformation(msg);
10908             pclose(commandOutput);
10909         }
10910     } else {
10911         if ((*cmailMsg) != '\0') {
10912             DisplayInformation(cmailMsg);
10913         }
10914     }
10915
10916     return;
10917 #endif /* !WIN32 */
10918 }
10919
10920 char *
10921 CmailMsg()
10922 {
10923 #if WIN32
10924     return NULL;
10925 #else
10926     int  prependComma = 0;
10927     char number[5];
10928     char string[MSG_SIZ];       /* Space for game-list */
10929     int  i;
10930     
10931     if (!cmailMsgLoaded) return "";
10932
10933     if (cmailMailedMove) {
10934         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10935     } else {
10936         /* Create a list of games left */
10937         sprintf(string, "[");
10938         for (i = 0; i < nCmailGames; i ++) {
10939             if (! (   cmailMoveRegistered[i]
10940                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10941                 if (prependComma) {
10942                     sprintf(number, ",%d", i + 1);
10943                 } else {
10944                     sprintf(number, "%d", i + 1);
10945                     prependComma = 1;
10946                 }
10947                 
10948                 strcat(string, number);
10949             }
10950         }
10951         strcat(string, "]");
10952
10953         if (nCmailMovesRegistered + nCmailResults == 0) {
10954             switch (nCmailGames) {
10955               case 1:
10956                 sprintf(cmailMsg,
10957                         _("Still need to make move for game\n"));
10958                 break;
10959                 
10960               case 2:
10961                 sprintf(cmailMsg,
10962                         _("Still need to make moves for both games\n"));
10963                 break;
10964                 
10965               default:
10966                 sprintf(cmailMsg,
10967                         _("Still need to make moves for all %d games\n"),
10968                         nCmailGames);
10969                 break;
10970             }
10971         } else {
10972             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10973               case 1:
10974                 sprintf(cmailMsg,
10975                         _("Still need to make a move for game %s\n"),
10976                         string);
10977                 break;
10978                 
10979               case 0:
10980                 if (nCmailResults == nCmailGames) {
10981                     sprintf(cmailMsg, _("No unfinished games\n"));
10982                 } else {
10983                     sprintf(cmailMsg, _("Ready to send mail\n"));
10984                 }
10985                 break;
10986                 
10987               default:
10988                 sprintf(cmailMsg,
10989                         _("Still need to make moves for games %s\n"),
10990                         string);
10991             }
10992         }
10993     }
10994     return cmailMsg;
10995 #endif /* WIN32 */
10996 }
10997
10998 void
10999 ResetGameEvent()
11000 {
11001     if (gameMode == Training)
11002       SetTrainingModeOff();
11003
11004     Reset(TRUE, TRUE);
11005     cmailMsgLoaded = FALSE;
11006     if (appData.icsActive) {
11007       SendToICS(ics_prefix);
11008       SendToICS("refresh\n");
11009     }
11010 }
11011
11012 void
11013 ExitEvent(status)
11014      int status;
11015 {
11016     exiting++;
11017     if (exiting > 2) {
11018       /* Give up on clean exit */
11019       exit(status);
11020     }
11021     if (exiting > 1) {
11022       /* Keep trying for clean exit */
11023       return;
11024     }
11025
11026     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11027
11028     if (telnetISR != NULL) {
11029       RemoveInputSource(telnetISR);
11030     }
11031     if (icsPR != NoProc) {
11032       DestroyChildProcess(icsPR, TRUE);
11033     }
11034
11035     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11036     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11037
11038     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11039     /* make sure this other one finishes before killing it!                  */
11040     if(endingGame) { int count = 0;
11041         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11042         while(endingGame && count++ < 10) DoSleep(1);
11043         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11044     }
11045
11046     /* Kill off chess programs */
11047     if (first.pr != NoProc) {
11048         ExitAnalyzeMode();
11049         
11050         DoSleep( appData.delayBeforeQuit );
11051         SendToProgram("quit\n", &first);
11052         DoSleep( appData.delayAfterQuit );
11053         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11054     }
11055     if (second.pr != NoProc) {
11056         DoSleep( appData.delayBeforeQuit );
11057         SendToProgram("quit\n", &second);
11058         DoSleep( appData.delayAfterQuit );
11059         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11060     }
11061     if (first.isr != NULL) {
11062         RemoveInputSource(first.isr);
11063     }
11064     if (second.isr != NULL) {
11065         RemoveInputSource(second.isr);
11066     }
11067
11068     ShutDownFrontEnd();
11069     exit(status);
11070 }
11071
11072 void
11073 PauseEvent()
11074 {
11075     if (appData.debugMode)
11076         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11077     if (pausing) {
11078         pausing = FALSE;
11079         ModeHighlight();
11080         if (gameMode == MachinePlaysWhite ||
11081             gameMode == MachinePlaysBlack) {
11082             StartClocks();
11083         } else {
11084             DisplayBothClocks();
11085         }
11086         if (gameMode == PlayFromGameFile) {
11087             if (appData.timeDelay >= 0) 
11088                 AutoPlayGameLoop();
11089         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11090             Reset(FALSE, TRUE);
11091             SendToICS(ics_prefix);
11092             SendToICS("refresh\n");
11093         } else if (currentMove < forwardMostMove) {
11094             ForwardInner(forwardMostMove);
11095         }
11096         pauseExamInvalid = FALSE;
11097     } else {
11098         switch (gameMode) {
11099           default:
11100             return;
11101           case IcsExamining:
11102             pauseExamForwardMostMove = forwardMostMove;
11103             pauseExamInvalid = FALSE;
11104             /* fall through */
11105           case IcsObserving:
11106           case IcsPlayingWhite:
11107           case IcsPlayingBlack:
11108             pausing = TRUE;
11109             ModeHighlight();
11110             return;
11111           case PlayFromGameFile:
11112             (void) StopLoadGameTimer();
11113             pausing = TRUE;
11114             ModeHighlight();
11115             break;
11116           case BeginningOfGame:
11117             if (appData.icsActive) return;
11118             /* else fall through */
11119           case MachinePlaysWhite:
11120           case MachinePlaysBlack:
11121           case TwoMachinesPlay:
11122             if (forwardMostMove == 0)
11123               return;           /* don't pause if no one has moved */
11124             if ((gameMode == MachinePlaysWhite &&
11125                  !WhiteOnMove(forwardMostMove)) ||
11126                 (gameMode == MachinePlaysBlack &&
11127                  WhiteOnMove(forwardMostMove))) {
11128                 StopClocks();
11129             }
11130             pausing = TRUE;
11131             ModeHighlight();
11132             break;
11133         }
11134     }
11135 }
11136
11137 void
11138 EditCommentEvent()
11139 {
11140     char title[MSG_SIZ];
11141
11142     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11143         strcpy(title, _("Edit comment"));
11144     } else {
11145         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11146                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11147                 parseList[currentMove - 1]);
11148     }
11149
11150     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11151 }
11152
11153
11154 void
11155 EditTagsEvent()
11156 {
11157     char *tags = PGNTags(&gameInfo);
11158     EditTagsPopUp(tags);
11159     free(tags);
11160 }
11161
11162 void
11163 AnalyzeModeEvent()
11164 {
11165     if (appData.noChessProgram || gameMode == AnalyzeMode)
11166       return;
11167
11168     if (gameMode != AnalyzeFile) {
11169         if (!appData.icsEngineAnalyze) {
11170                EditGameEvent();
11171                if (gameMode != EditGame) return;
11172         }
11173         ResurrectChessProgram();
11174         SendToProgram("analyze\n", &first);
11175         first.analyzing = TRUE;
11176         /*first.maybeThinking = TRUE;*/
11177         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11178         EngineOutputPopUp();
11179     }
11180     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11181     pausing = FALSE;
11182     ModeHighlight();
11183     SetGameInfo();
11184
11185     StartAnalysisClock();
11186     GetTimeMark(&lastNodeCountTime);
11187     lastNodeCount = 0;
11188 }
11189
11190 void
11191 AnalyzeFileEvent()
11192 {
11193     if (appData.noChessProgram || gameMode == AnalyzeFile)
11194       return;
11195
11196     if (gameMode != AnalyzeMode) {
11197         EditGameEvent();
11198         if (gameMode != EditGame) return;
11199         ResurrectChessProgram();
11200         SendToProgram("analyze\n", &first);
11201         first.analyzing = TRUE;
11202         /*first.maybeThinking = TRUE;*/
11203         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11204         EngineOutputPopUp();
11205     }
11206     gameMode = AnalyzeFile;
11207     pausing = FALSE;
11208     ModeHighlight();
11209     SetGameInfo();
11210
11211     StartAnalysisClock();
11212     GetTimeMark(&lastNodeCountTime);
11213     lastNodeCount = 0;
11214 }
11215
11216 void
11217 MachineWhiteEvent()
11218 {
11219     char buf[MSG_SIZ];
11220     char *bookHit = NULL;
11221
11222     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11223       return;
11224
11225
11226     if (gameMode == PlayFromGameFile || 
11227         gameMode == TwoMachinesPlay  || 
11228         gameMode == Training         || 
11229         gameMode == AnalyzeMode      || 
11230         gameMode == EndOfGame)
11231         EditGameEvent();
11232
11233     if (gameMode == EditPosition) 
11234         EditPositionDone(TRUE);
11235
11236     if (!WhiteOnMove(currentMove)) {
11237         DisplayError(_("It is not White's turn"), 0);
11238         return;
11239     }
11240   
11241     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11242       ExitAnalyzeMode();
11243
11244     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11245         gameMode == AnalyzeFile)
11246         TruncateGame();
11247
11248     ResurrectChessProgram();    /* in case it isn't running */
11249     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11250         gameMode = MachinePlaysWhite;
11251         ResetClocks();
11252     } else
11253     gameMode = MachinePlaysWhite;
11254     pausing = FALSE;
11255     ModeHighlight();
11256     SetGameInfo();
11257     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11258     DisplayTitle(buf);
11259     if (first.sendName) {
11260       sprintf(buf, "name %s\n", gameInfo.black);
11261       SendToProgram(buf, &first);
11262     }
11263     if (first.sendTime) {
11264       if (first.useColors) {
11265         SendToProgram("black\n", &first); /*gnu kludge*/
11266       }
11267       SendTimeRemaining(&first, TRUE);
11268     }
11269     if (first.useColors) {
11270       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11271     }
11272     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11273     SetMachineThinkingEnables();
11274     first.maybeThinking = TRUE;
11275     StartClocks();
11276     firstMove = FALSE;
11277
11278     if (appData.autoFlipView && !flipView) {
11279       flipView = !flipView;
11280       DrawPosition(FALSE, NULL);
11281       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11282     }
11283
11284     if(bookHit) { // [HGM] book: simulate book reply
11285         static char bookMove[MSG_SIZ]; // a bit generous?
11286
11287         programStats.nodes = programStats.depth = programStats.time = 
11288         programStats.score = programStats.got_only_move = 0;
11289         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11290
11291         strcpy(bookMove, "move ");
11292         strcat(bookMove, bookHit);
11293         HandleMachineMove(bookMove, &first);
11294     }
11295 }
11296
11297 void
11298 MachineBlackEvent()
11299 {
11300     char buf[MSG_SIZ];
11301    char *bookHit = NULL;
11302
11303     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11304         return;
11305
11306
11307     if (gameMode == PlayFromGameFile || 
11308         gameMode == TwoMachinesPlay  || 
11309         gameMode == Training         || 
11310         gameMode == AnalyzeMode      || 
11311         gameMode == EndOfGame)
11312         EditGameEvent();
11313
11314     if (gameMode == EditPosition) 
11315         EditPositionDone(TRUE);
11316
11317     if (WhiteOnMove(currentMove)) {
11318         DisplayError(_("It is not Black's turn"), 0);
11319         return;
11320     }
11321     
11322     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11323       ExitAnalyzeMode();
11324
11325     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11326         gameMode == AnalyzeFile)
11327         TruncateGame();
11328
11329     ResurrectChessProgram();    /* in case it isn't running */
11330     gameMode = MachinePlaysBlack;
11331     pausing = FALSE;
11332     ModeHighlight();
11333     SetGameInfo();
11334     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11335     DisplayTitle(buf);
11336     if (first.sendName) {
11337       sprintf(buf, "name %s\n", gameInfo.white);
11338       SendToProgram(buf, &first);
11339     }
11340     if (first.sendTime) {
11341       if (first.useColors) {
11342         SendToProgram("white\n", &first); /*gnu kludge*/
11343       }
11344       SendTimeRemaining(&first, FALSE);
11345     }
11346     if (first.useColors) {
11347       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11348     }
11349     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11350     SetMachineThinkingEnables();
11351     first.maybeThinking = TRUE;
11352     StartClocks();
11353
11354     if (appData.autoFlipView && flipView) {
11355       flipView = !flipView;
11356       DrawPosition(FALSE, NULL);
11357       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11358     }
11359     if(bookHit) { // [HGM] book: simulate book reply
11360         static char bookMove[MSG_SIZ]; // a bit generous?
11361
11362         programStats.nodes = programStats.depth = programStats.time = 
11363         programStats.score = programStats.got_only_move = 0;
11364         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11365
11366         strcpy(bookMove, "move ");
11367         strcat(bookMove, bookHit);
11368         HandleMachineMove(bookMove, &first);
11369     }
11370 }
11371
11372
11373 void
11374 DisplayTwoMachinesTitle()
11375 {
11376     char buf[MSG_SIZ];
11377     if (appData.matchGames > 0) {
11378         if (first.twoMachinesColor[0] == 'w') {
11379             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11380                     gameInfo.white, gameInfo.black,
11381                     first.matchWins, second.matchWins,
11382                     matchGame - 1 - (first.matchWins + second.matchWins));
11383         } else {
11384             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11385                     gameInfo.white, gameInfo.black,
11386                     second.matchWins, first.matchWins,
11387                     matchGame - 1 - (first.matchWins + second.matchWins));
11388         }
11389     } else {
11390         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11391     }
11392     DisplayTitle(buf);
11393 }
11394
11395 void
11396 TwoMachinesEvent P((void))
11397 {
11398     int i;
11399     char buf[MSG_SIZ];
11400     ChessProgramState *onmove;
11401     char *bookHit = NULL;
11402     
11403     if (appData.noChessProgram) return;
11404
11405     switch (gameMode) {
11406       case TwoMachinesPlay:
11407         return;
11408       case MachinePlaysWhite:
11409       case MachinePlaysBlack:
11410         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11411             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11412             return;
11413         }
11414         /* fall through */
11415       case BeginningOfGame:
11416       case PlayFromGameFile:
11417       case EndOfGame:
11418         EditGameEvent();
11419         if (gameMode != EditGame) return;
11420         break;
11421       case EditPosition:
11422         EditPositionDone(TRUE);
11423         break;
11424       case AnalyzeMode:
11425       case AnalyzeFile:
11426         ExitAnalyzeMode();
11427         break;
11428       case EditGame:
11429       default:
11430         break;
11431     }
11432
11433 //    forwardMostMove = currentMove;
11434     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11435     ResurrectChessProgram();    /* in case first program isn't running */
11436
11437     if (second.pr == NULL) {
11438         StartChessProgram(&second);
11439         if (second.protocolVersion == 1) {
11440           TwoMachinesEventIfReady();
11441         } else {
11442           /* kludge: allow timeout for initial "feature" command */
11443           FreezeUI();
11444           DisplayMessage("", _("Starting second chess program"));
11445           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11446         }
11447         return;
11448     }
11449     DisplayMessage("", "");
11450     InitChessProgram(&second, FALSE);
11451     SendToProgram("force\n", &second);
11452     if (startedFromSetupPosition) {
11453         SendBoard(&second, backwardMostMove);
11454     if (appData.debugMode) {
11455         fprintf(debugFP, "Two Machines\n");
11456     }
11457     }
11458     for (i = backwardMostMove; i < forwardMostMove; i++) {
11459         SendMoveToProgram(i, &second);
11460     }
11461
11462     gameMode = TwoMachinesPlay;
11463     pausing = FALSE;
11464     ModeHighlight();
11465     SetGameInfo();
11466     DisplayTwoMachinesTitle();
11467     firstMove = TRUE;
11468     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11469         onmove = &first;
11470     } else {
11471         onmove = &second;
11472     }
11473
11474     SendToProgram(first.computerString, &first);
11475     if (first.sendName) {
11476       sprintf(buf, "name %s\n", second.tidy);
11477       SendToProgram(buf, &first);
11478     }
11479     SendToProgram(second.computerString, &second);
11480     if (second.sendName) {
11481       sprintf(buf, "name %s\n", first.tidy);
11482       SendToProgram(buf, &second);
11483     }
11484
11485     ResetClocks();
11486     if (!first.sendTime || !second.sendTime) {
11487         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11488         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11489     }
11490     if (onmove->sendTime) {
11491       if (onmove->useColors) {
11492         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11493       }
11494       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11495     }
11496     if (onmove->useColors) {
11497       SendToProgram(onmove->twoMachinesColor, onmove);
11498     }
11499     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11500 //    SendToProgram("go\n", onmove);
11501     onmove->maybeThinking = TRUE;
11502     SetMachineThinkingEnables();
11503
11504     StartClocks();
11505
11506     if(bookHit) { // [HGM] book: simulate book reply
11507         static char bookMove[MSG_SIZ]; // a bit generous?
11508
11509         programStats.nodes = programStats.depth = programStats.time = 
11510         programStats.score = programStats.got_only_move = 0;
11511         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11512
11513         strcpy(bookMove, "move ");
11514         strcat(bookMove, bookHit);
11515         savedMessage = bookMove; // args for deferred call
11516         savedState = onmove;
11517         ScheduleDelayedEvent(DeferredBookMove, 1);
11518     }
11519 }
11520
11521 void
11522 TrainingEvent()
11523 {
11524     if (gameMode == Training) {
11525       SetTrainingModeOff();
11526       gameMode = PlayFromGameFile;
11527       DisplayMessage("", _("Training mode off"));
11528     } else {
11529       gameMode = Training;
11530       animateTraining = appData.animate;
11531
11532       /* make sure we are not already at the end of the game */
11533       if (currentMove < forwardMostMove) {
11534         SetTrainingModeOn();
11535         DisplayMessage("", _("Training mode on"));
11536       } else {
11537         gameMode = PlayFromGameFile;
11538         DisplayError(_("Already at end of game"), 0);
11539       }
11540     }
11541     ModeHighlight();
11542 }
11543
11544 void
11545 IcsClientEvent()
11546 {
11547     if (!appData.icsActive) return;
11548     switch (gameMode) {
11549       case IcsPlayingWhite:
11550       case IcsPlayingBlack:
11551       case IcsObserving:
11552       case IcsIdle:
11553       case BeginningOfGame:
11554       case IcsExamining:
11555         return;
11556
11557       case EditGame:
11558         break;
11559
11560       case EditPosition:
11561         EditPositionDone(TRUE);
11562         break;
11563
11564       case AnalyzeMode:
11565       case AnalyzeFile:
11566         ExitAnalyzeMode();
11567         break;
11568         
11569       default:
11570         EditGameEvent();
11571         break;
11572     }
11573
11574     gameMode = IcsIdle;
11575     ModeHighlight();
11576     return;
11577 }
11578
11579
11580 void
11581 EditGameEvent()
11582 {
11583     int i;
11584
11585     switch (gameMode) {
11586       case Training:
11587         SetTrainingModeOff();
11588         break;
11589       case MachinePlaysWhite:
11590       case MachinePlaysBlack:
11591       case BeginningOfGame:
11592         SendToProgram("force\n", &first);
11593         SetUserThinkingEnables();
11594         break;
11595       case PlayFromGameFile:
11596         (void) StopLoadGameTimer();
11597         if (gameFileFP != NULL) {
11598             gameFileFP = NULL;
11599         }
11600         break;
11601       case EditPosition:
11602         EditPositionDone(TRUE);
11603         break;
11604       case AnalyzeMode:
11605       case AnalyzeFile:
11606         ExitAnalyzeMode();
11607         SendToProgram("force\n", &first);
11608         break;
11609       case TwoMachinesPlay:
11610         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11611         ResurrectChessProgram();
11612         SetUserThinkingEnables();
11613         break;
11614       case EndOfGame:
11615         ResurrectChessProgram();
11616         break;
11617       case IcsPlayingBlack:
11618       case IcsPlayingWhite:
11619         DisplayError(_("Warning: You are still playing a game"), 0);
11620         break;
11621       case IcsObserving:
11622         DisplayError(_("Warning: You are still observing a game"), 0);
11623         break;
11624       case IcsExamining:
11625         DisplayError(_("Warning: You are still examining a game"), 0);
11626         break;
11627       case IcsIdle:
11628         break;
11629       case EditGame:
11630       default:
11631         return;
11632     }
11633     
11634     pausing = FALSE;
11635     StopClocks();
11636     first.offeredDraw = second.offeredDraw = 0;
11637
11638     if (gameMode == PlayFromGameFile) {
11639         whiteTimeRemaining = timeRemaining[0][currentMove];
11640         blackTimeRemaining = timeRemaining[1][currentMove];
11641         DisplayTitle("");
11642     }
11643
11644     if (gameMode == MachinePlaysWhite ||
11645         gameMode == MachinePlaysBlack ||
11646         gameMode == TwoMachinesPlay ||
11647         gameMode == EndOfGame) {
11648         i = forwardMostMove;
11649         while (i > currentMove) {
11650             SendToProgram("undo\n", &first);
11651             i--;
11652         }
11653         whiteTimeRemaining = timeRemaining[0][currentMove];
11654         blackTimeRemaining = timeRemaining[1][currentMove];
11655         DisplayBothClocks();
11656         if (whiteFlag || blackFlag) {
11657             whiteFlag = blackFlag = 0;
11658         }
11659         DisplayTitle("");
11660     }           
11661     
11662     gameMode = EditGame;
11663     ModeHighlight();
11664     SetGameInfo();
11665 }
11666
11667
11668 void
11669 EditPositionEvent()
11670 {
11671     if (gameMode == EditPosition) {
11672         EditGameEvent();
11673         return;
11674     }
11675     
11676     EditGameEvent();
11677     if (gameMode != EditGame) return;
11678     
11679     gameMode = EditPosition;
11680     ModeHighlight();
11681     SetGameInfo();
11682     if (currentMove > 0)
11683       CopyBoard(boards[0], boards[currentMove]);
11684     
11685     blackPlaysFirst = !WhiteOnMove(currentMove);
11686     ResetClocks();
11687     currentMove = forwardMostMove = backwardMostMove = 0;
11688     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11689     DisplayMove(-1);
11690 }
11691
11692 void
11693 ExitAnalyzeMode()
11694 {
11695     /* [DM] icsEngineAnalyze - possible call from other functions */
11696     if (appData.icsEngineAnalyze) {
11697         appData.icsEngineAnalyze = FALSE;
11698
11699         DisplayMessage("",_("Close ICS engine analyze..."));
11700     }
11701     if (first.analysisSupport && first.analyzing) {
11702       SendToProgram("exit\n", &first);
11703       first.analyzing = FALSE;
11704     }
11705     thinkOutput[0] = NULLCHAR;
11706 }
11707
11708 void
11709 EditPositionDone(Boolean fakeRights)
11710 {
11711     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11712
11713     startedFromSetupPosition = TRUE;
11714     InitChessProgram(&first, FALSE);
11715     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11716       boards[0][EP_STATUS] = EP_NONE;
11717       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11718     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11719         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11720         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11721       } else boards[0][CASTLING][2] = NoRights;
11722     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11723         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11724         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11725       } else boards[0][CASTLING][5] = NoRights;
11726     }
11727     SendToProgram("force\n", &first);
11728     if (blackPlaysFirst) {
11729         strcpy(moveList[0], "");
11730         strcpy(parseList[0], "");
11731         currentMove = forwardMostMove = backwardMostMove = 1;
11732         CopyBoard(boards[1], boards[0]);
11733     } else {
11734         currentMove = forwardMostMove = backwardMostMove = 0;
11735     }
11736     SendBoard(&first, forwardMostMove);
11737     if (appData.debugMode) {
11738         fprintf(debugFP, "EditPosDone\n");
11739     }
11740     DisplayTitle("");
11741     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11742     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11743     gameMode = EditGame;
11744     ModeHighlight();
11745     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11746     ClearHighlights(); /* [AS] */
11747 }
11748
11749 /* Pause for `ms' milliseconds */
11750 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11751 void
11752 TimeDelay(ms)
11753      long ms;
11754 {
11755     TimeMark m1, m2;
11756
11757     GetTimeMark(&m1);
11758     do {
11759         GetTimeMark(&m2);
11760     } while (SubtractTimeMarks(&m2, &m1) < ms);
11761 }
11762
11763 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11764 void
11765 SendMultiLineToICS(buf)
11766      char *buf;
11767 {
11768     char temp[MSG_SIZ+1], *p;
11769     int len;
11770
11771     len = strlen(buf);
11772     if (len > MSG_SIZ)
11773       len = MSG_SIZ;
11774   
11775     strncpy(temp, buf, len);
11776     temp[len] = 0;
11777
11778     p = temp;
11779     while (*p) {
11780         if (*p == '\n' || *p == '\r')
11781           *p = ' ';
11782         ++p;
11783     }
11784
11785     strcat(temp, "\n");
11786     SendToICS(temp);
11787     SendToPlayer(temp, strlen(temp));
11788 }
11789
11790 void
11791 SetWhiteToPlayEvent()
11792 {
11793     if (gameMode == EditPosition) {
11794         blackPlaysFirst = FALSE;
11795         DisplayBothClocks();    /* works because currentMove is 0 */
11796     } else if (gameMode == IcsExamining) {
11797         SendToICS(ics_prefix);
11798         SendToICS("tomove white\n");
11799     }
11800 }
11801
11802 void
11803 SetBlackToPlayEvent()
11804 {
11805     if (gameMode == EditPosition) {
11806         blackPlaysFirst = TRUE;
11807         currentMove = 1;        /* kludge */
11808         DisplayBothClocks();
11809         currentMove = 0;
11810     } else if (gameMode == IcsExamining) {
11811         SendToICS(ics_prefix);
11812         SendToICS("tomove black\n");
11813     }
11814 }
11815
11816 void
11817 EditPositionMenuEvent(selection, x, y)
11818      ChessSquare selection;
11819      int x, y;
11820 {
11821     char buf[MSG_SIZ];
11822     ChessSquare piece = boards[0][y][x];
11823
11824     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11825
11826     switch (selection) {
11827       case ClearBoard:
11828         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11829             SendToICS(ics_prefix);
11830             SendToICS("bsetup clear\n");
11831         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11832             SendToICS(ics_prefix);
11833             SendToICS("clearboard\n");
11834         } else {
11835             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11836                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11837                 for (y = 0; y < BOARD_HEIGHT; y++) {
11838                     if (gameMode == IcsExamining) {
11839                         if (boards[currentMove][y][x] != EmptySquare) {
11840                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11841                                     AAA + x, ONE + y);
11842                             SendToICS(buf);
11843                         }
11844                     } else {
11845                         boards[0][y][x] = p;
11846                     }
11847                 }
11848             }
11849         }
11850         if (gameMode == EditPosition) {
11851             DrawPosition(FALSE, boards[0]);
11852         }
11853         break;
11854
11855       case WhitePlay:
11856         SetWhiteToPlayEvent();
11857         break;
11858
11859       case BlackPlay:
11860         SetBlackToPlayEvent();
11861         break;
11862
11863       case EmptySquare:
11864         if (gameMode == IcsExamining) {
11865             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11866             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11867             SendToICS(buf);
11868         } else {
11869             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11870                 if(x == BOARD_LEFT-2) {
11871                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11872                     boards[0][y][1] = 0;
11873                 } else
11874                 if(x == BOARD_RGHT+1) {
11875                     if(y >= gameInfo.holdingsSize) break;
11876                     boards[0][y][BOARD_WIDTH-2] = 0;
11877                 } else break;
11878             }
11879             boards[0][y][x] = EmptySquare;
11880             DrawPosition(FALSE, boards[0]);
11881         }
11882         break;
11883
11884       case PromotePiece:
11885         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11886            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
11887             selection = (ChessSquare) (PROMOTED piece);
11888         } else if(piece == EmptySquare) selection = WhiteSilver;
11889         else selection = (ChessSquare)((int)piece - 1);
11890         goto defaultlabel;
11891
11892       case DemotePiece:
11893         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11894            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
11895             selection = (ChessSquare) (DEMOTED piece);
11896         } else if(piece == EmptySquare) selection = BlackSilver;
11897         else selection = (ChessSquare)((int)piece + 1);       
11898         goto defaultlabel;
11899
11900       case WhiteQueen:
11901       case BlackQueen:
11902         if(gameInfo.variant == VariantShatranj ||
11903            gameInfo.variant == VariantXiangqi  ||
11904            gameInfo.variant == VariantCourier  ||
11905            gameInfo.variant == VariantMakruk     )
11906             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11907         goto defaultlabel;
11908
11909       case WhiteKing:
11910       case BlackKing:
11911         if(gameInfo.variant == VariantXiangqi)
11912             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11913         if(gameInfo.variant == VariantKnightmate)
11914             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11915       default:
11916         defaultlabel:
11917         if (gameMode == IcsExamining) {
11918             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11919             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11920                     PieceToChar(selection), AAA + x, ONE + y);
11921             SendToICS(buf);
11922         } else {
11923             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11924                 int n;
11925                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11926                     n = PieceToNumber(selection - BlackPawn);
11927                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11928                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
11929                     boards[0][BOARD_HEIGHT-1-n][1]++;
11930                 } else
11931                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11932                     n = PieceToNumber(selection);
11933                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11934                     boards[0][n][BOARD_WIDTH-1] = selection;
11935                     boards[0][n][BOARD_WIDTH-2]++;
11936                 }
11937             } else
11938             boards[0][y][x] = selection;
11939             DrawPosition(TRUE, boards[0]);
11940         }
11941         break;
11942     }
11943 }
11944
11945
11946 void
11947 DropMenuEvent(selection, x, y)
11948      ChessSquare selection;
11949      int x, y;
11950 {
11951     ChessMove moveType;
11952
11953     switch (gameMode) {
11954       case IcsPlayingWhite:
11955       case MachinePlaysBlack:
11956         if (!WhiteOnMove(currentMove)) {
11957             DisplayMoveError(_("It is Black's turn"));
11958             return;
11959         }
11960         moveType = WhiteDrop;
11961         break;
11962       case IcsPlayingBlack:
11963       case MachinePlaysWhite:
11964         if (WhiteOnMove(currentMove)) {
11965             DisplayMoveError(_("It is White's turn"));
11966             return;
11967         }
11968         moveType = BlackDrop;
11969         break;
11970       case EditGame:
11971         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11972         break;
11973       default:
11974         return;
11975     }
11976
11977     if (moveType == BlackDrop && selection < BlackPawn) {
11978       selection = (ChessSquare) ((int) selection
11979                                  + (int) BlackPawn - (int) WhitePawn);
11980     }
11981     if (boards[currentMove][y][x] != EmptySquare) {
11982         DisplayMoveError(_("That square is occupied"));
11983         return;
11984     }
11985
11986     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11987 }
11988
11989 void
11990 AcceptEvent()
11991 {
11992     /* Accept a pending offer of any kind from opponent */
11993     
11994     if (appData.icsActive) {
11995         SendToICS(ics_prefix);
11996         SendToICS("accept\n");
11997     } else if (cmailMsgLoaded) {
11998         if (currentMove == cmailOldMove &&
11999             commentList[cmailOldMove] != NULL &&
12000             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12001                    "Black offers a draw" : "White offers a draw")) {
12002             TruncateGame();
12003             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12004             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12005         } else {
12006             DisplayError(_("There is no pending offer on this move"), 0);
12007             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12008         }
12009     } else {
12010         /* Not used for offers from chess program */
12011     }
12012 }
12013
12014 void
12015 DeclineEvent()
12016 {
12017     /* Decline a pending offer of any kind from opponent */
12018     
12019     if (appData.icsActive) {
12020         SendToICS(ics_prefix);
12021         SendToICS("decline\n");
12022     } else if (cmailMsgLoaded) {
12023         if (currentMove == cmailOldMove &&
12024             commentList[cmailOldMove] != NULL &&
12025             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12026                    "Black offers a draw" : "White offers a draw")) {
12027 #ifdef NOTDEF
12028             AppendComment(cmailOldMove, "Draw declined", TRUE);
12029             DisplayComment(cmailOldMove - 1, "Draw declined");
12030 #endif /*NOTDEF*/
12031         } else {
12032             DisplayError(_("There is no pending offer on this move"), 0);
12033         }
12034     } else {
12035         /* Not used for offers from chess program */
12036     }
12037 }
12038
12039 void
12040 RematchEvent()
12041 {
12042     /* Issue ICS rematch command */
12043     if (appData.icsActive) {
12044         SendToICS(ics_prefix);
12045         SendToICS("rematch\n");
12046     }
12047 }
12048
12049 void
12050 CallFlagEvent()
12051 {
12052     /* Call your opponent's flag (claim a win on time) */
12053     if (appData.icsActive) {
12054         SendToICS(ics_prefix);
12055         SendToICS("flag\n");
12056     } else {
12057         switch (gameMode) {
12058           default:
12059             return;
12060           case MachinePlaysWhite:
12061             if (whiteFlag) {
12062                 if (blackFlag)
12063                   GameEnds(GameIsDrawn, "Both players ran out of time",
12064                            GE_PLAYER);
12065                 else
12066                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12067             } else {
12068                 DisplayError(_("Your opponent is not out of time"), 0);
12069             }
12070             break;
12071           case MachinePlaysBlack:
12072             if (blackFlag) {
12073                 if (whiteFlag)
12074                   GameEnds(GameIsDrawn, "Both players ran out of time",
12075                            GE_PLAYER);
12076                 else
12077                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12078             } else {
12079                 DisplayError(_("Your opponent is not out of time"), 0);
12080             }
12081             break;
12082         }
12083     }
12084 }
12085
12086 void
12087 DrawEvent()
12088 {
12089     /* Offer draw or accept pending draw offer from opponent */
12090     
12091     if (appData.icsActive) {
12092         /* Note: tournament rules require draw offers to be
12093            made after you make your move but before you punch
12094            your clock.  Currently ICS doesn't let you do that;
12095            instead, you immediately punch your clock after making
12096            a move, but you can offer a draw at any time. */
12097         
12098         SendToICS(ics_prefix);
12099         SendToICS("draw\n");
12100         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12101     } else if (cmailMsgLoaded) {
12102         if (currentMove == cmailOldMove &&
12103             commentList[cmailOldMove] != NULL &&
12104             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12105                    "Black offers a draw" : "White offers a draw")) {
12106             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12107             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12108         } else if (currentMove == cmailOldMove + 1) {
12109             char *offer = WhiteOnMove(cmailOldMove) ?
12110               "White offers a draw" : "Black offers a draw";
12111             AppendComment(currentMove, offer, TRUE);
12112             DisplayComment(currentMove - 1, offer);
12113             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12114         } else {
12115             DisplayError(_("You must make your move before offering a draw"), 0);
12116             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12117         }
12118     } else if (first.offeredDraw) {
12119         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12120     } else {
12121         if (first.sendDrawOffers) {
12122             SendToProgram("draw\n", &first);
12123             userOfferedDraw = TRUE;
12124         }
12125     }
12126 }
12127
12128 void
12129 AdjournEvent()
12130 {
12131     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12132     
12133     if (appData.icsActive) {
12134         SendToICS(ics_prefix);
12135         SendToICS("adjourn\n");
12136     } else {
12137         /* Currently GNU Chess doesn't offer or accept Adjourns */
12138     }
12139 }
12140
12141
12142 void
12143 AbortEvent()
12144 {
12145     /* Offer Abort or accept pending Abort offer from opponent */
12146     
12147     if (appData.icsActive) {
12148         SendToICS(ics_prefix);
12149         SendToICS("abort\n");
12150     } else {
12151         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12152     }
12153 }
12154
12155 void
12156 ResignEvent()
12157 {
12158     /* Resign.  You can do this even if it's not your turn. */
12159     
12160     if (appData.icsActive) {
12161         SendToICS(ics_prefix);
12162         SendToICS("resign\n");
12163     } else {
12164         switch (gameMode) {
12165           case MachinePlaysWhite:
12166             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12167             break;
12168           case MachinePlaysBlack:
12169             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12170             break;
12171           case EditGame:
12172             if (cmailMsgLoaded) {
12173                 TruncateGame();
12174                 if (WhiteOnMove(cmailOldMove)) {
12175                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12176                 } else {
12177                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12178                 }
12179                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12180             }
12181             break;
12182           default:
12183             break;
12184         }
12185     }
12186 }
12187
12188
12189 void
12190 StopObservingEvent()
12191 {
12192     /* Stop observing current games */
12193     SendToICS(ics_prefix);
12194     SendToICS("unobserve\n");
12195 }
12196
12197 void
12198 StopExaminingEvent()
12199 {
12200     /* Stop observing current game */
12201     SendToICS(ics_prefix);
12202     SendToICS("unexamine\n");
12203 }
12204
12205 void
12206 ForwardInner(target)
12207      int target;
12208 {
12209     int limit;
12210
12211     if (appData.debugMode)
12212         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12213                 target, currentMove, forwardMostMove);
12214
12215     if (gameMode == EditPosition)
12216       return;
12217
12218     if (gameMode == PlayFromGameFile && !pausing)
12219       PauseEvent();
12220     
12221     if (gameMode == IcsExamining && pausing)
12222       limit = pauseExamForwardMostMove;
12223     else
12224       limit = forwardMostMove;
12225     
12226     if (target > limit) target = limit;
12227
12228     if (target > 0 && moveList[target - 1][0]) {
12229         int fromX, fromY, toX, toY;
12230         toX = moveList[target - 1][2] - AAA;
12231         toY = moveList[target - 1][3] - ONE;
12232         if (moveList[target - 1][1] == '@') {
12233             if (appData.highlightLastMove) {
12234                 SetHighlights(-1, -1, toX, toY);
12235             }
12236         } else {
12237             fromX = moveList[target - 1][0] - AAA;
12238             fromY = moveList[target - 1][1] - ONE;
12239             if (target == currentMove + 1) {
12240                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12241             }
12242             if (appData.highlightLastMove) {
12243                 SetHighlights(fromX, fromY, toX, toY);
12244             }
12245         }
12246     }
12247     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12248         gameMode == Training || gameMode == PlayFromGameFile || 
12249         gameMode == AnalyzeFile) {
12250         while (currentMove < target) {
12251             SendMoveToProgram(currentMove++, &first);
12252         }
12253     } else {
12254         currentMove = target;
12255     }
12256     
12257     if (gameMode == EditGame || gameMode == EndOfGame) {
12258         whiteTimeRemaining = timeRemaining[0][currentMove];
12259         blackTimeRemaining = timeRemaining[1][currentMove];
12260     }
12261     DisplayBothClocks();
12262     DisplayMove(currentMove - 1);
12263     DrawPosition(FALSE, boards[currentMove]);
12264     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12265     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12266         DisplayComment(currentMove - 1, commentList[currentMove]);
12267     }
12268 }
12269
12270
12271 void
12272 ForwardEvent()
12273 {
12274     if (gameMode == IcsExamining && !pausing) {
12275         SendToICS(ics_prefix);
12276         SendToICS("forward\n");
12277     } else {
12278         ForwardInner(currentMove + 1);
12279     }
12280 }
12281
12282 void
12283 ToEndEvent()
12284 {
12285     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12286         /* to optimze, we temporarily turn off analysis mode while we feed
12287          * the remaining moves to the engine. Otherwise we get analysis output
12288          * after each move.
12289          */ 
12290         if (first.analysisSupport) {
12291           SendToProgram("exit\nforce\n", &first);
12292           first.analyzing = FALSE;
12293         }
12294     }
12295         
12296     if (gameMode == IcsExamining && !pausing) {
12297         SendToICS(ics_prefix);
12298         SendToICS("forward 999999\n");
12299     } else {
12300         ForwardInner(forwardMostMove);
12301     }
12302
12303     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12304         /* we have fed all the moves, so reactivate analysis mode */
12305         SendToProgram("analyze\n", &first);
12306         first.analyzing = TRUE;
12307         /*first.maybeThinking = TRUE;*/
12308         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12309     }
12310 }
12311
12312 void
12313 BackwardInner(target)
12314      int target;
12315 {
12316     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12317
12318     if (appData.debugMode)
12319         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12320                 target, currentMove, forwardMostMove);
12321
12322     if (gameMode == EditPosition) return;
12323     if (currentMove <= backwardMostMove) {
12324         ClearHighlights();
12325         DrawPosition(full_redraw, boards[currentMove]);
12326         return;
12327     }
12328     if (gameMode == PlayFromGameFile && !pausing)
12329       PauseEvent();
12330     
12331     if (moveList[target][0]) {
12332         int fromX, fromY, toX, toY;
12333         toX = moveList[target][2] - AAA;
12334         toY = moveList[target][3] - ONE;
12335         if (moveList[target][1] == '@') {
12336             if (appData.highlightLastMove) {
12337                 SetHighlights(-1, -1, toX, toY);
12338             }
12339         } else {
12340             fromX = moveList[target][0] - AAA;
12341             fromY = moveList[target][1] - ONE;
12342             if (target == currentMove - 1) {
12343                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12344             }
12345             if (appData.highlightLastMove) {
12346                 SetHighlights(fromX, fromY, toX, toY);
12347             }
12348         }
12349     }
12350     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12351         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12352         while (currentMove > target) {
12353             SendToProgram("undo\n", &first);
12354             currentMove--;
12355         }
12356     } else {
12357         currentMove = target;
12358     }
12359     
12360     if (gameMode == EditGame || gameMode == EndOfGame) {
12361         whiteTimeRemaining = timeRemaining[0][currentMove];
12362         blackTimeRemaining = timeRemaining[1][currentMove];
12363     }
12364     DisplayBothClocks();
12365     DisplayMove(currentMove - 1);
12366     DrawPosition(full_redraw, boards[currentMove]);
12367     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12368     // [HGM] PV info: routine tests if comment empty
12369     DisplayComment(currentMove - 1, commentList[currentMove]);
12370 }
12371
12372 void
12373 BackwardEvent()
12374 {
12375     if (gameMode == IcsExamining && !pausing) {
12376         SendToICS(ics_prefix);
12377         SendToICS("backward\n");
12378     } else {
12379         BackwardInner(currentMove - 1);
12380     }
12381 }
12382
12383 void
12384 ToStartEvent()
12385 {
12386     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12387         /* to optimize, we temporarily turn off analysis mode while we undo
12388          * all the moves. Otherwise we get analysis output after each undo.
12389          */ 
12390         if (first.analysisSupport) {
12391           SendToProgram("exit\nforce\n", &first);
12392           first.analyzing = FALSE;
12393         }
12394     }
12395
12396     if (gameMode == IcsExamining && !pausing) {
12397         SendToICS(ics_prefix);
12398         SendToICS("backward 999999\n");
12399     } else {
12400         BackwardInner(backwardMostMove);
12401     }
12402
12403     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12404         /* we have fed all the moves, so reactivate analysis mode */
12405         SendToProgram("analyze\n", &first);
12406         first.analyzing = TRUE;
12407         /*first.maybeThinking = TRUE;*/
12408         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12409     }
12410 }
12411
12412 void
12413 ToNrEvent(int to)
12414 {
12415   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12416   if (to >= forwardMostMove) to = forwardMostMove;
12417   if (to <= backwardMostMove) to = backwardMostMove;
12418   if (to < currentMove) {
12419     BackwardInner(to);
12420   } else {
12421     ForwardInner(to);
12422   }
12423 }
12424
12425 void
12426 RevertEvent()
12427 {
12428     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12429         return;
12430     }
12431     if (gameMode != IcsExamining) {
12432         DisplayError(_("You are not examining a game"), 0);
12433         return;
12434     }
12435     if (pausing) {
12436         DisplayError(_("You can't revert while pausing"), 0);
12437         return;
12438     }
12439     SendToICS(ics_prefix);
12440     SendToICS("revert\n");
12441 }
12442
12443 void
12444 RetractMoveEvent()
12445 {
12446     switch (gameMode) {
12447       case MachinePlaysWhite:
12448       case MachinePlaysBlack:
12449         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12450             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12451             return;
12452         }
12453         if (forwardMostMove < 2) return;
12454         currentMove = forwardMostMove = forwardMostMove - 2;
12455         whiteTimeRemaining = timeRemaining[0][currentMove];
12456         blackTimeRemaining = timeRemaining[1][currentMove];
12457         DisplayBothClocks();
12458         DisplayMove(currentMove - 1);
12459         ClearHighlights();/*!! could figure this out*/
12460         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12461         SendToProgram("remove\n", &first);
12462         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12463         break;
12464
12465       case BeginningOfGame:
12466       default:
12467         break;
12468
12469       case IcsPlayingWhite:
12470       case IcsPlayingBlack:
12471         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12472             SendToICS(ics_prefix);
12473             SendToICS("takeback 2\n");
12474         } else {
12475             SendToICS(ics_prefix);
12476             SendToICS("takeback 1\n");
12477         }
12478         break;
12479     }
12480 }
12481
12482 void
12483 MoveNowEvent()
12484 {
12485     ChessProgramState *cps;
12486
12487     switch (gameMode) {
12488       case MachinePlaysWhite:
12489         if (!WhiteOnMove(forwardMostMove)) {
12490             DisplayError(_("It is your turn"), 0);
12491             return;
12492         }
12493         cps = &first;
12494         break;
12495       case MachinePlaysBlack:
12496         if (WhiteOnMove(forwardMostMove)) {
12497             DisplayError(_("It is your turn"), 0);
12498             return;
12499         }
12500         cps = &first;
12501         break;
12502       case TwoMachinesPlay:
12503         if (WhiteOnMove(forwardMostMove) ==
12504             (first.twoMachinesColor[0] == 'w')) {
12505             cps = &first;
12506         } else {
12507             cps = &second;
12508         }
12509         break;
12510       case BeginningOfGame:
12511       default:
12512         return;
12513     }
12514     SendToProgram("?\n", cps);
12515 }
12516
12517 void
12518 TruncateGameEvent()
12519 {
12520     EditGameEvent();
12521     if (gameMode != EditGame) return;
12522     TruncateGame();
12523 }
12524
12525 void
12526 TruncateGame()
12527 {
12528     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12529     if (forwardMostMove > currentMove) {
12530         if (gameInfo.resultDetails != NULL) {
12531             free(gameInfo.resultDetails);
12532             gameInfo.resultDetails = NULL;
12533             gameInfo.result = GameUnfinished;
12534         }
12535         forwardMostMove = currentMove;
12536         HistorySet(parseList, backwardMostMove, forwardMostMove,
12537                    currentMove-1);
12538     }
12539 }
12540
12541 void
12542 HintEvent()
12543 {
12544     if (appData.noChessProgram) return;
12545     switch (gameMode) {
12546       case MachinePlaysWhite:
12547         if (WhiteOnMove(forwardMostMove)) {
12548             DisplayError(_("Wait until your turn"), 0);
12549             return;
12550         }
12551         break;
12552       case BeginningOfGame:
12553       case MachinePlaysBlack:
12554         if (!WhiteOnMove(forwardMostMove)) {
12555             DisplayError(_("Wait until your turn"), 0);
12556             return;
12557         }
12558         break;
12559       default:
12560         DisplayError(_("No hint available"), 0);
12561         return;
12562     }
12563     SendToProgram("hint\n", &first);
12564     hintRequested = TRUE;
12565 }
12566
12567 void
12568 BookEvent()
12569 {
12570     if (appData.noChessProgram) return;
12571     switch (gameMode) {
12572       case MachinePlaysWhite:
12573         if (WhiteOnMove(forwardMostMove)) {
12574             DisplayError(_("Wait until your turn"), 0);
12575             return;
12576         }
12577         break;
12578       case BeginningOfGame:
12579       case MachinePlaysBlack:
12580         if (!WhiteOnMove(forwardMostMove)) {
12581             DisplayError(_("Wait until your turn"), 0);
12582             return;
12583         }
12584         break;
12585       case EditPosition:
12586         EditPositionDone(TRUE);
12587         break;
12588       case TwoMachinesPlay:
12589         return;
12590       default:
12591         break;
12592     }
12593     SendToProgram("bk\n", &first);
12594     bookOutput[0] = NULLCHAR;
12595     bookRequested = TRUE;
12596 }
12597
12598 void
12599 AboutGameEvent()
12600 {
12601     char *tags = PGNTags(&gameInfo);
12602     TagsPopUp(tags, CmailMsg());
12603     free(tags);
12604 }
12605
12606 /* end button procedures */
12607
12608 void
12609 PrintPosition(fp, move)
12610      FILE *fp;
12611      int move;
12612 {
12613     int i, j;
12614     
12615     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12616         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12617             char c = PieceToChar(boards[move][i][j]);
12618             fputc(c == 'x' ? '.' : c, fp);
12619             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12620         }
12621     }
12622     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12623       fprintf(fp, "white to play\n");
12624     else
12625       fprintf(fp, "black to play\n");
12626 }
12627
12628 void
12629 PrintOpponents(fp)
12630      FILE *fp;
12631 {
12632     if (gameInfo.white != NULL) {
12633         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12634     } else {
12635         fprintf(fp, "\n");
12636     }
12637 }
12638
12639 /* Find last component of program's own name, using some heuristics */
12640 void
12641 TidyProgramName(prog, host, buf)
12642      char *prog, *host, buf[MSG_SIZ];
12643 {
12644     char *p, *q;
12645     int local = (strcmp(host, "localhost") == 0);
12646     while (!local && (p = strchr(prog, ';')) != NULL) {
12647         p++;
12648         while (*p == ' ') p++;
12649         prog = p;
12650     }
12651     if (*prog == '"' || *prog == '\'') {
12652         q = strchr(prog + 1, *prog);
12653     } else {
12654         q = strchr(prog, ' ');
12655     }
12656     if (q == NULL) q = prog + strlen(prog);
12657     p = q;
12658     while (p >= prog && *p != '/' && *p != '\\') p--;
12659     p++;
12660     if(p == prog && *p == '"') p++;
12661     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12662     memcpy(buf, p, q - p);
12663     buf[q - p] = NULLCHAR;
12664     if (!local) {
12665         strcat(buf, "@");
12666         strcat(buf, host);
12667     }
12668 }
12669
12670 char *
12671 TimeControlTagValue()
12672 {
12673     char buf[MSG_SIZ];
12674     if (!appData.clockMode) {
12675         strcpy(buf, "-");
12676     } else if (movesPerSession > 0) {
12677         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12678     } else if (timeIncrement == 0) {
12679         sprintf(buf, "%ld", timeControl/1000);
12680     } else {
12681         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12682     }
12683     return StrSave(buf);
12684 }
12685
12686 void
12687 SetGameInfo()
12688 {
12689     /* This routine is used only for certain modes */
12690     VariantClass v = gameInfo.variant;
12691     ChessMove r = GameUnfinished;
12692     char *p = NULL;
12693
12694     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12695         r = gameInfo.result; 
12696         p = gameInfo.resultDetails; 
12697         gameInfo.resultDetails = NULL;
12698     }
12699     ClearGameInfo(&gameInfo);
12700     gameInfo.variant = v;
12701
12702     switch (gameMode) {
12703       case MachinePlaysWhite:
12704         gameInfo.event = StrSave( appData.pgnEventHeader );
12705         gameInfo.site = StrSave(HostName());
12706         gameInfo.date = PGNDate();
12707         gameInfo.round = StrSave("-");
12708         gameInfo.white = StrSave(first.tidy);
12709         gameInfo.black = StrSave(UserName());
12710         gameInfo.timeControl = TimeControlTagValue();
12711         break;
12712
12713       case MachinePlaysBlack:
12714         gameInfo.event = StrSave( appData.pgnEventHeader );
12715         gameInfo.site = StrSave(HostName());
12716         gameInfo.date = PGNDate();
12717         gameInfo.round = StrSave("-");
12718         gameInfo.white = StrSave(UserName());
12719         gameInfo.black = StrSave(first.tidy);
12720         gameInfo.timeControl = TimeControlTagValue();
12721         break;
12722
12723       case TwoMachinesPlay:
12724         gameInfo.event = StrSave( appData.pgnEventHeader );
12725         gameInfo.site = StrSave(HostName());
12726         gameInfo.date = PGNDate();
12727         if (matchGame > 0) {
12728             char buf[MSG_SIZ];
12729             sprintf(buf, "%d", matchGame);
12730             gameInfo.round = StrSave(buf);
12731         } else {
12732             gameInfo.round = StrSave("-");
12733         }
12734         if (first.twoMachinesColor[0] == 'w') {
12735             gameInfo.white = StrSave(first.tidy);
12736             gameInfo.black = StrSave(second.tidy);
12737         } else {
12738             gameInfo.white = StrSave(second.tidy);
12739             gameInfo.black = StrSave(first.tidy);
12740         }
12741         gameInfo.timeControl = TimeControlTagValue();
12742         break;
12743
12744       case EditGame:
12745         gameInfo.event = StrSave("Edited game");
12746         gameInfo.site = StrSave(HostName());
12747         gameInfo.date = PGNDate();
12748         gameInfo.round = StrSave("-");
12749         gameInfo.white = StrSave("-");
12750         gameInfo.black = StrSave("-");
12751         gameInfo.result = r;
12752         gameInfo.resultDetails = p;
12753         break;
12754
12755       case EditPosition:
12756         gameInfo.event = StrSave("Edited position");
12757         gameInfo.site = StrSave(HostName());
12758         gameInfo.date = PGNDate();
12759         gameInfo.round = StrSave("-");
12760         gameInfo.white = StrSave("-");
12761         gameInfo.black = StrSave("-");
12762         break;
12763
12764       case IcsPlayingWhite:
12765       case IcsPlayingBlack:
12766       case IcsObserving:
12767       case IcsExamining:
12768         break;
12769
12770       case PlayFromGameFile:
12771         gameInfo.event = StrSave("Game from non-PGN file");
12772         gameInfo.site = StrSave(HostName());
12773         gameInfo.date = PGNDate();
12774         gameInfo.round = StrSave("-");
12775         gameInfo.white = StrSave("?");
12776         gameInfo.black = StrSave("?");
12777         break;
12778
12779       default:
12780         break;
12781     }
12782 }
12783
12784 void
12785 ReplaceComment(index, text)
12786      int index;
12787      char *text;
12788 {
12789     int len;
12790
12791     while (*text == '\n') text++;
12792     len = strlen(text);
12793     while (len > 0 && text[len - 1] == '\n') len--;
12794
12795     if (commentList[index] != NULL)
12796       free(commentList[index]);
12797
12798     if (len == 0) {
12799         commentList[index] = NULL;
12800         return;
12801     }
12802   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12803       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12804       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12805     commentList[index] = (char *) malloc(len + 2);
12806     strncpy(commentList[index], text, len);
12807     commentList[index][len] = '\n';
12808     commentList[index][len + 1] = NULLCHAR;
12809   } else { 
12810     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12811     char *p;
12812     commentList[index] = (char *) malloc(len + 6);
12813     strcpy(commentList[index], "{\n");
12814     strncpy(commentList[index]+2, text, len);
12815     commentList[index][len+2] = NULLCHAR;
12816     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12817     strcat(commentList[index], "\n}\n");
12818   }
12819 }
12820
12821 void
12822 CrushCRs(text)
12823      char *text;
12824 {
12825   char *p = text;
12826   char *q = text;
12827   char ch;
12828
12829   do {
12830     ch = *p++;
12831     if (ch == '\r') continue;
12832     *q++ = ch;
12833   } while (ch != '\0');
12834 }
12835
12836 void
12837 AppendComment(index, text, addBraces)
12838      int index;
12839      char *text;
12840      Boolean addBraces; // [HGM] braces: tells if we should add {}
12841 {
12842     int oldlen, len;
12843     char *old;
12844
12845 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12846     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12847
12848     CrushCRs(text);
12849     while (*text == '\n') text++;
12850     len = strlen(text);
12851     while (len > 0 && text[len - 1] == '\n') len--;
12852
12853     if (len == 0) return;
12854
12855     if (commentList[index] != NULL) {
12856         old = commentList[index];
12857         oldlen = strlen(old);
12858         while(commentList[index][oldlen-1] ==  '\n')
12859           commentList[index][--oldlen] = NULLCHAR;
12860         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12861         strcpy(commentList[index], old);
12862         free(old);
12863         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12864         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12865           if(addBraces) addBraces = FALSE; else { text++; len--; }
12866           while (*text == '\n') { text++; len--; }
12867           commentList[index][--oldlen] = NULLCHAR;
12868       }
12869         if(addBraces) strcat(commentList[index], "\n{\n");
12870         else          strcat(commentList[index], "\n");
12871         strcat(commentList[index], text);
12872         if(addBraces) strcat(commentList[index], "\n}\n");
12873         else          strcat(commentList[index], "\n");
12874     } else {
12875         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12876         if(addBraces)
12877              strcpy(commentList[index], "{\n");
12878         else commentList[index][0] = NULLCHAR;
12879         strcat(commentList[index], text);
12880         strcat(commentList[index], "\n");
12881         if(addBraces) strcat(commentList[index], "}\n");
12882     }
12883 }
12884
12885 static char * FindStr( char * text, char * sub_text )
12886 {
12887     char * result = strstr( text, sub_text );
12888
12889     if( result != NULL ) {
12890         result += strlen( sub_text );
12891     }
12892
12893     return result;
12894 }
12895
12896 /* [AS] Try to extract PV info from PGN comment */
12897 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12898 char *GetInfoFromComment( int index, char * text )
12899 {
12900     char * sep = text;
12901
12902     if( text != NULL && index > 0 ) {
12903         int score = 0;
12904         int depth = 0;
12905         int time = -1, sec = 0, deci;
12906         char * s_eval = FindStr( text, "[%eval " );
12907         char * s_emt = FindStr( text, "[%emt " );
12908
12909         if( s_eval != NULL || s_emt != NULL ) {
12910             /* New style */
12911             char delim;
12912
12913             if( s_eval != NULL ) {
12914                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12915                     return text;
12916                 }
12917
12918                 if( delim != ']' ) {
12919                     return text;
12920                 }
12921             }
12922
12923             if( s_emt != NULL ) {
12924             }
12925                 return text;
12926         }
12927         else {
12928             /* We expect something like: [+|-]nnn.nn/dd */
12929             int score_lo = 0;
12930
12931             if(*text != '{') return text; // [HGM] braces: must be normal comment
12932
12933             sep = strchr( text, '/' );
12934             if( sep == NULL || sep < (text+4) ) {
12935                 return text;
12936             }
12937
12938             time = -1; sec = -1; deci = -1;
12939             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12940                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12941                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12942                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
12943                 return text;
12944             }
12945
12946             if( score_lo < 0 || score_lo >= 100 ) {
12947                 return text;
12948             }
12949
12950             if(sec >= 0) time = 600*time + 10*sec; else
12951             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12952
12953             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12954
12955             /* [HGM] PV time: now locate end of PV info */
12956             while( *++sep >= '0' && *sep <= '9'); // strip depth
12957             if(time >= 0)
12958             while( *++sep >= '0' && *sep <= '9'); // strip time
12959             if(sec >= 0)
12960             while( *++sep >= '0' && *sep <= '9'); // strip seconds
12961             if(deci >= 0)
12962             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12963             while(*sep == ' ') sep++;
12964         }
12965
12966         if( depth <= 0 ) {
12967             return text;
12968         }
12969
12970         if( time < 0 ) {
12971             time = -1;
12972         }
12973
12974         pvInfoList[index-1].depth = depth;
12975         pvInfoList[index-1].score = score;
12976         pvInfoList[index-1].time  = 10*time; // centi-sec
12977         if(*sep == '}') *sep = 0; else *--sep = '{';
12978     }
12979     return sep;
12980 }
12981
12982 void
12983 SendToProgram(message, cps)
12984      char *message;
12985      ChessProgramState *cps;
12986 {
12987     int count, outCount, error;
12988     char buf[MSG_SIZ];
12989
12990     if (cps->pr == NULL) return;
12991     Attention(cps);
12992     
12993     if (appData.debugMode) {
12994         TimeMark now;
12995         GetTimeMark(&now);
12996         fprintf(debugFP, "%ld >%-6s: %s", 
12997                 SubtractTimeMarks(&now, &programStartTime),
12998                 cps->which, message);
12999     }
13000     
13001     count = strlen(message);
13002     outCount = OutputToProcess(cps->pr, message, count, &error);
13003     if (outCount < count && !exiting 
13004                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13005         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13006         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13007             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13008                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13009                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13010             } else {
13011                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13012             }
13013             gameInfo.resultDetails = StrSave(buf);
13014         }
13015         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13016     }
13017 }
13018
13019 void
13020 ReceiveFromProgram(isr, closure, message, count, error)
13021      InputSourceRef isr;
13022      VOIDSTAR closure;
13023      char *message;
13024      int count;
13025      int error;
13026 {
13027     char *end_str;
13028     char buf[MSG_SIZ];
13029     ChessProgramState *cps = (ChessProgramState *)closure;
13030
13031     if (isr != cps->isr) return; /* Killed intentionally */
13032     if (count <= 0) {
13033         if (count == 0) {
13034             sprintf(buf,
13035                     _("Error: %s chess program (%s) exited unexpectedly"),
13036                     cps->which, cps->program);
13037         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13038                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13039                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13040                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13041                 } else {
13042                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13043                 }
13044                 gameInfo.resultDetails = StrSave(buf);
13045             }
13046             RemoveInputSource(cps->isr);
13047             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13048         } else {
13049             sprintf(buf,
13050                     _("Error reading from %s chess program (%s)"),
13051                     cps->which, cps->program);
13052             RemoveInputSource(cps->isr);
13053
13054             /* [AS] Program is misbehaving badly... kill it */
13055             if( count == -2 ) {
13056                 DestroyChildProcess( cps->pr, 9 );
13057                 cps->pr = NoProc;
13058             }
13059
13060             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13061         }
13062         return;
13063     }
13064     
13065     if ((end_str = strchr(message, '\r')) != NULL)
13066       *end_str = NULLCHAR;
13067     if ((end_str = strchr(message, '\n')) != NULL)
13068       *end_str = NULLCHAR;
13069     
13070     if (appData.debugMode) {
13071         TimeMark now; int print = 1;
13072         char *quote = ""; char c; int i;
13073
13074         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13075                 char start = message[0];
13076                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13077                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13078                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13079                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13080                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13081                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13082                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13083                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13084                         { quote = "# "; print = (appData.engineComments == 2); }
13085                 message[0] = start; // restore original message
13086         }
13087         if(print) {
13088                 GetTimeMark(&now);
13089                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13090                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13091                         quote,
13092                         message);
13093         }
13094     }
13095
13096     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13097     if (appData.icsEngineAnalyze) {
13098         if (strstr(message, "whisper") != NULL ||
13099              strstr(message, "kibitz") != NULL || 
13100             strstr(message, "tellics") != NULL) return;
13101     }
13102
13103     HandleMachineMove(message, cps);
13104 }
13105
13106
13107 void
13108 SendTimeControl(cps, mps, tc, inc, sd, st)
13109      ChessProgramState *cps;
13110      int mps, inc, sd, st;
13111      long tc;
13112 {
13113     char buf[MSG_SIZ];
13114     int seconds;
13115
13116     if( timeControl_2 > 0 ) {
13117         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13118             tc = timeControl_2;
13119         }
13120     }
13121     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13122     inc /= cps->timeOdds;
13123     st  /= cps->timeOdds;
13124
13125     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13126
13127     if (st > 0) {
13128       /* Set exact time per move, normally using st command */
13129       if (cps->stKludge) {
13130         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13131         seconds = st % 60;
13132         if (seconds == 0) {
13133           sprintf(buf, "level 1 %d\n", st/60);
13134         } else {
13135           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13136         }
13137       } else {
13138         sprintf(buf, "st %d\n", st);
13139       }
13140     } else {
13141       /* Set conventional or incremental time control, using level command */
13142       if (seconds == 0) {
13143         /* Note old gnuchess bug -- minutes:seconds used to not work.
13144            Fixed in later versions, but still avoid :seconds
13145            when seconds is 0. */
13146         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13147       } else {
13148         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13149                 seconds, inc/1000);
13150       }
13151     }
13152     SendToProgram(buf, cps);
13153
13154     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13155     /* Orthogonally, limit search to given depth */
13156     if (sd > 0) {
13157       if (cps->sdKludge) {
13158         sprintf(buf, "depth\n%d\n", sd);
13159       } else {
13160         sprintf(buf, "sd %d\n", sd);
13161       }
13162       SendToProgram(buf, cps);
13163     }
13164
13165     if(cps->nps > 0) { /* [HGM] nps */
13166         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13167         else {
13168                 sprintf(buf, "nps %d\n", cps->nps);
13169               SendToProgram(buf, cps);
13170         }
13171     }
13172 }
13173
13174 ChessProgramState *WhitePlayer()
13175 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13176 {
13177     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13178        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13179         return &second;
13180     return &first;
13181 }
13182
13183 void
13184 SendTimeRemaining(cps, machineWhite)
13185      ChessProgramState *cps;
13186      int /*boolean*/ machineWhite;
13187 {
13188     char message[MSG_SIZ];
13189     long time, otime;
13190
13191     /* Note: this routine must be called when the clocks are stopped
13192        or when they have *just* been set or switched; otherwise
13193        it will be off by the time since the current tick started.
13194     */
13195     if (machineWhite) {
13196         time = whiteTimeRemaining / 10;
13197         otime = blackTimeRemaining / 10;
13198     } else {
13199         time = blackTimeRemaining / 10;
13200         otime = whiteTimeRemaining / 10;
13201     }
13202     /* [HGM] translate opponent's time by time-odds factor */
13203     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13204     if (appData.debugMode) {
13205         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13206     }
13207
13208     if (time <= 0) time = 1;
13209     if (otime <= 0) otime = 1;
13210     
13211     sprintf(message, "time %ld\n", time);
13212     SendToProgram(message, cps);
13213
13214     sprintf(message, "otim %ld\n", otime);
13215     SendToProgram(message, cps);
13216 }
13217
13218 int
13219 BoolFeature(p, name, loc, cps)
13220      char **p;
13221      char *name;
13222      int *loc;
13223      ChessProgramState *cps;
13224 {
13225   char buf[MSG_SIZ];
13226   int len = strlen(name);
13227   int val;
13228   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13229     (*p) += len + 1;
13230     sscanf(*p, "%d", &val);
13231     *loc = (val != 0);
13232     while (**p && **p != ' ') (*p)++;
13233     sprintf(buf, "accepted %s\n", name);
13234     SendToProgram(buf, cps);
13235     return TRUE;
13236   }
13237   return FALSE;
13238 }
13239
13240 int
13241 IntFeature(p, name, loc, cps)
13242      char **p;
13243      char *name;
13244      int *loc;
13245      ChessProgramState *cps;
13246 {
13247   char buf[MSG_SIZ];
13248   int len = strlen(name);
13249   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13250     (*p) += len + 1;
13251     sscanf(*p, "%d", loc);
13252     while (**p && **p != ' ') (*p)++;
13253     sprintf(buf, "accepted %s\n", name);
13254     SendToProgram(buf, cps);
13255     return TRUE;
13256   }
13257   return FALSE;
13258 }
13259
13260 int
13261 StringFeature(p, name, loc, cps)
13262      char **p;
13263      char *name;
13264      char loc[];
13265      ChessProgramState *cps;
13266 {
13267   char buf[MSG_SIZ];
13268   int len = strlen(name);
13269   if (strncmp((*p), name, len) == 0
13270       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13271     (*p) += len + 2;
13272     sscanf(*p, "%[^\"]", loc);
13273     while (**p && **p != '\"') (*p)++;
13274     if (**p == '\"') (*p)++;
13275     sprintf(buf, "accepted %s\n", name);
13276     SendToProgram(buf, cps);
13277     return TRUE;
13278   }
13279   return FALSE;
13280 }
13281
13282 int 
13283 ParseOption(Option *opt, ChessProgramState *cps)
13284 // [HGM] options: process the string that defines an engine option, and determine
13285 // name, type, default value, and allowed value range
13286 {
13287         char *p, *q, buf[MSG_SIZ];
13288         int n, min = (-1)<<31, max = 1<<31, def;
13289
13290         if(p = strstr(opt->name, " -spin ")) {
13291             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13292             if(max < min) max = min; // enforce consistency
13293             if(def < min) def = min;
13294             if(def > max) def = max;
13295             opt->value = def;
13296             opt->min = min;
13297             opt->max = max;
13298             opt->type = Spin;
13299         } else if((p = strstr(opt->name, " -slider "))) {
13300             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13301             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13302             if(max < min) max = min; // enforce consistency
13303             if(def < min) def = min;
13304             if(def > max) def = max;
13305             opt->value = def;
13306             opt->min = min;
13307             opt->max = max;
13308             opt->type = Spin; // Slider;
13309         } else if((p = strstr(opt->name, " -string "))) {
13310             opt->textValue = p+9;
13311             opt->type = TextBox;
13312         } else if((p = strstr(opt->name, " -file "))) {
13313             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13314             opt->textValue = p+7;
13315             opt->type = TextBox; // FileName;
13316         } else if((p = strstr(opt->name, " -path "))) {
13317             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13318             opt->textValue = p+7;
13319             opt->type = TextBox; // PathName;
13320         } else if(p = strstr(opt->name, " -check ")) {
13321             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13322             opt->value = (def != 0);
13323             opt->type = CheckBox;
13324         } else if(p = strstr(opt->name, " -combo ")) {
13325             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13326             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13327             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13328             opt->value = n = 0;
13329             while(q = StrStr(q, " /// ")) {
13330                 n++; *q = 0;    // count choices, and null-terminate each of them
13331                 q += 5;
13332                 if(*q == '*') { // remember default, which is marked with * prefix
13333                     q++;
13334                     opt->value = n;
13335                 }
13336                 cps->comboList[cps->comboCnt++] = q;
13337             }
13338             cps->comboList[cps->comboCnt++] = NULL;
13339             opt->max = n + 1;
13340             opt->type = ComboBox;
13341         } else if(p = strstr(opt->name, " -button")) {
13342             opt->type = Button;
13343         } else if(p = strstr(opt->name, " -save")) {
13344             opt->type = SaveButton;
13345         } else return FALSE;
13346         *p = 0; // terminate option name
13347         // now look if the command-line options define a setting for this engine option.
13348         if(cps->optionSettings && cps->optionSettings[0])
13349             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13350         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13351                 sprintf(buf, "option %s", p);
13352                 if(p = strstr(buf, ",")) *p = 0;
13353                 strcat(buf, "\n");
13354                 SendToProgram(buf, cps);
13355         }
13356         return TRUE;
13357 }
13358
13359 void
13360 FeatureDone(cps, val)
13361      ChessProgramState* cps;
13362      int val;
13363 {
13364   DelayedEventCallback cb = GetDelayedEvent();
13365   if ((cb == InitBackEnd3 && cps == &first) ||
13366       (cb == TwoMachinesEventIfReady && cps == &second)) {
13367     CancelDelayedEvent();
13368     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13369   }
13370   cps->initDone = val;
13371 }
13372
13373 /* Parse feature command from engine */
13374 void
13375 ParseFeatures(args, cps)
13376      char* args;
13377      ChessProgramState *cps;  
13378 {
13379   char *p = args;
13380   char *q;
13381   int val;
13382   char buf[MSG_SIZ];
13383
13384   for (;;) {
13385     while (*p == ' ') p++;
13386     if (*p == NULLCHAR) return;
13387
13388     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13389     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13390     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13391     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13392     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13393     if (BoolFeature(&p, "reuse", &val, cps)) {
13394       /* Engine can disable reuse, but can't enable it if user said no */
13395       if (!val) cps->reuse = FALSE;
13396       continue;
13397     }
13398     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13399     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13400       if (gameMode == TwoMachinesPlay) {
13401         DisplayTwoMachinesTitle();
13402       } else {
13403         DisplayTitle("");
13404       }
13405       continue;
13406     }
13407     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13408     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13409     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13410     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13411     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13412     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13413     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13414     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13415     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13416     if (IntFeature(&p, "done", &val, cps)) {
13417       FeatureDone(cps, val);
13418       continue;
13419     }
13420     /* Added by Tord: */
13421     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13422     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13423     /* End of additions by Tord */
13424
13425     /* [HGM] added features: */
13426     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13427     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13428     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13429     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13430     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13431     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13432     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13433         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13434             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13435             SendToProgram(buf, cps);
13436             continue;
13437         }
13438         if(cps->nrOptions >= MAX_OPTIONS) {
13439             cps->nrOptions--;
13440             sprintf(buf, "%s engine has too many options\n", cps->which);
13441             DisplayError(buf, 0);
13442         }
13443         continue;
13444     }
13445     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13446     /* End of additions by HGM */
13447
13448     /* unknown feature: complain and skip */
13449     q = p;
13450     while (*q && *q != '=') q++;
13451     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13452     SendToProgram(buf, cps);
13453     p = q;
13454     if (*p == '=') {
13455       p++;
13456       if (*p == '\"') {
13457         p++;
13458         while (*p && *p != '\"') p++;
13459         if (*p == '\"') p++;
13460       } else {
13461         while (*p && *p != ' ') p++;
13462       }
13463     }
13464   }
13465
13466 }
13467
13468 void
13469 PeriodicUpdatesEvent(newState)
13470      int newState;
13471 {
13472     if (newState == appData.periodicUpdates)
13473       return;
13474
13475     appData.periodicUpdates=newState;
13476
13477     /* Display type changes, so update it now */
13478 //    DisplayAnalysis();
13479
13480     /* Get the ball rolling again... */
13481     if (newState) {
13482         AnalysisPeriodicEvent(1);
13483         StartAnalysisClock();
13484     }
13485 }
13486
13487 void
13488 PonderNextMoveEvent(newState)
13489      int newState;
13490 {
13491     if (newState == appData.ponderNextMove) return;
13492     if (gameMode == EditPosition) EditPositionDone(TRUE);
13493     if (newState) {
13494         SendToProgram("hard\n", &first);
13495         if (gameMode == TwoMachinesPlay) {
13496             SendToProgram("hard\n", &second);
13497         }
13498     } else {
13499         SendToProgram("easy\n", &first);
13500         thinkOutput[0] = NULLCHAR;
13501         if (gameMode == TwoMachinesPlay) {
13502             SendToProgram("easy\n", &second);
13503         }
13504     }
13505     appData.ponderNextMove = newState;
13506 }
13507
13508 void
13509 NewSettingEvent(option, command, value)
13510      char *command;
13511      int option, value;
13512 {
13513     char buf[MSG_SIZ];
13514
13515     if (gameMode == EditPosition) EditPositionDone(TRUE);
13516     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13517     SendToProgram(buf, &first);
13518     if (gameMode == TwoMachinesPlay) {
13519         SendToProgram(buf, &second);
13520     }
13521 }
13522
13523 void
13524 ShowThinkingEvent()
13525 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13526 {
13527     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13528     int newState = appData.showThinking
13529         // [HGM] thinking: other features now need thinking output as well
13530         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13531     
13532     if (oldState == newState) return;
13533     oldState = newState;
13534     if (gameMode == EditPosition) EditPositionDone(TRUE);
13535     if (oldState) {
13536         SendToProgram("post\n", &first);
13537         if (gameMode == TwoMachinesPlay) {
13538             SendToProgram("post\n", &second);
13539         }
13540     } else {
13541         SendToProgram("nopost\n", &first);
13542         thinkOutput[0] = NULLCHAR;
13543         if (gameMode == TwoMachinesPlay) {
13544             SendToProgram("nopost\n", &second);
13545         }
13546     }
13547 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13548 }
13549
13550 void
13551 AskQuestionEvent(title, question, replyPrefix, which)
13552      char *title; char *question; char *replyPrefix; char *which;
13553 {
13554   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13555   if (pr == NoProc) return;
13556   AskQuestion(title, question, replyPrefix, pr);
13557 }
13558
13559 void
13560 DisplayMove(moveNumber)
13561      int moveNumber;
13562 {
13563     char message[MSG_SIZ];
13564     char res[MSG_SIZ];
13565     char cpThinkOutput[MSG_SIZ];
13566
13567     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13568     
13569     if (moveNumber == forwardMostMove - 1 || 
13570         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13571
13572         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13573
13574         if (strchr(cpThinkOutput, '\n')) {
13575             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13576         }
13577     } else {
13578         *cpThinkOutput = NULLCHAR;
13579     }
13580
13581     /* [AS] Hide thinking from human user */
13582     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13583         *cpThinkOutput = NULLCHAR;
13584         if( thinkOutput[0] != NULLCHAR ) {
13585             int i;
13586
13587             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13588                 cpThinkOutput[i] = '.';
13589             }
13590             cpThinkOutput[i] = NULLCHAR;
13591             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13592         }
13593     }
13594
13595     if (moveNumber == forwardMostMove - 1 &&
13596         gameInfo.resultDetails != NULL) {
13597         if (gameInfo.resultDetails[0] == NULLCHAR) {
13598             sprintf(res, " %s", PGNResult(gameInfo.result));
13599         } else {
13600             sprintf(res, " {%s} %s",
13601                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13602         }
13603     } else {
13604         res[0] = NULLCHAR;
13605     }
13606
13607     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13608         DisplayMessage(res, cpThinkOutput);
13609     } else {
13610         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13611                 WhiteOnMove(moveNumber) ? " " : ".. ",
13612                 parseList[moveNumber], res);
13613         DisplayMessage(message, cpThinkOutput);
13614     }
13615 }
13616
13617 void
13618 DisplayComment(moveNumber, text)
13619      int moveNumber;
13620      char *text;
13621 {
13622     char title[MSG_SIZ];
13623     char buf[8000]; // comment can be long!
13624     int score, depth;
13625     
13626     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13627       strcpy(title, "Comment");
13628     } else {
13629       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13630               WhiteOnMove(moveNumber) ? " " : ".. ",
13631               parseList[moveNumber]);
13632     }
13633     // [HGM] PV info: display PV info together with (or as) comment
13634     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13635       if(text == NULL) text = "";                                           
13636       score = pvInfoList[moveNumber].score;
13637       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13638               depth, (pvInfoList[moveNumber].time+50)/100, text);
13639       text = buf;
13640     }
13641     if (text != NULL && (appData.autoDisplayComment || commentUp))
13642         CommentPopUp(title, text);
13643 }
13644
13645 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13646  * might be busy thinking or pondering.  It can be omitted if your
13647  * gnuchess is configured to stop thinking immediately on any user
13648  * input.  However, that gnuchess feature depends on the FIONREAD
13649  * ioctl, which does not work properly on some flavors of Unix.
13650  */
13651 void
13652 Attention(cps)
13653      ChessProgramState *cps;
13654 {
13655 #if ATTENTION
13656     if (!cps->useSigint) return;
13657     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13658     switch (gameMode) {
13659       case MachinePlaysWhite:
13660       case MachinePlaysBlack:
13661       case TwoMachinesPlay:
13662       case IcsPlayingWhite:
13663       case IcsPlayingBlack:
13664       case AnalyzeMode:
13665       case AnalyzeFile:
13666         /* Skip if we know it isn't thinking */
13667         if (!cps->maybeThinking) return;
13668         if (appData.debugMode)
13669           fprintf(debugFP, "Interrupting %s\n", cps->which);
13670         InterruptChildProcess(cps->pr);
13671         cps->maybeThinking = FALSE;
13672         break;
13673       default:
13674         break;
13675     }
13676 #endif /*ATTENTION*/
13677 }
13678
13679 int
13680 CheckFlags()
13681 {
13682     if (whiteTimeRemaining <= 0) {
13683         if (!whiteFlag) {
13684             whiteFlag = TRUE;
13685             if (appData.icsActive) {
13686                 if (appData.autoCallFlag &&
13687                     gameMode == IcsPlayingBlack && !blackFlag) {
13688                   SendToICS(ics_prefix);
13689                   SendToICS("flag\n");
13690                 }
13691             } else {
13692                 if (blackFlag) {
13693                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13694                 } else {
13695                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13696                     if (appData.autoCallFlag) {
13697                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13698                         return TRUE;
13699                     }
13700                 }
13701             }
13702         }
13703     }
13704     if (blackTimeRemaining <= 0) {
13705         if (!blackFlag) {
13706             blackFlag = TRUE;
13707             if (appData.icsActive) {
13708                 if (appData.autoCallFlag &&
13709                     gameMode == IcsPlayingWhite && !whiteFlag) {
13710                   SendToICS(ics_prefix);
13711                   SendToICS("flag\n");
13712                 }
13713             } else {
13714                 if (whiteFlag) {
13715                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13716                 } else {
13717                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13718                     if (appData.autoCallFlag) {
13719                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13720                         return TRUE;
13721                     }
13722                 }
13723             }
13724         }
13725     }
13726     return FALSE;
13727 }
13728
13729 void
13730 CheckTimeControl()
13731 {
13732     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13733         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13734
13735     /*
13736      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13737      */
13738     if ( !WhiteOnMove(forwardMostMove) )
13739         /* White made time control */
13740         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13741         /* [HGM] time odds: correct new time quota for time odds! */
13742                                             / WhitePlayer()->timeOdds;
13743       else
13744         /* Black made time control */
13745         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13746                                             / WhitePlayer()->other->timeOdds;
13747 }
13748
13749 void
13750 DisplayBothClocks()
13751 {
13752     int wom = gameMode == EditPosition ?
13753       !blackPlaysFirst : WhiteOnMove(currentMove);
13754     DisplayWhiteClock(whiteTimeRemaining, wom);
13755     DisplayBlackClock(blackTimeRemaining, !wom);
13756 }
13757
13758
13759 /* Timekeeping seems to be a portability nightmare.  I think everyone
13760    has ftime(), but I'm really not sure, so I'm including some ifdefs
13761    to use other calls if you don't.  Clocks will be less accurate if
13762    you have neither ftime nor gettimeofday.
13763 */
13764
13765 /* VS 2008 requires the #include outside of the function */
13766 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13767 #include <sys/timeb.h>
13768 #endif
13769
13770 /* Get the current time as a TimeMark */
13771 void
13772 GetTimeMark(tm)
13773      TimeMark *tm;
13774 {
13775 #if HAVE_GETTIMEOFDAY
13776
13777     struct timeval timeVal;
13778     struct timezone timeZone;
13779
13780     gettimeofday(&timeVal, &timeZone);
13781     tm->sec = (long) timeVal.tv_sec; 
13782     tm->ms = (int) (timeVal.tv_usec / 1000L);
13783
13784 #else /*!HAVE_GETTIMEOFDAY*/
13785 #if HAVE_FTIME
13786
13787 // include <sys/timeb.h> / moved to just above start of function
13788     struct timeb timeB;
13789
13790     ftime(&timeB);
13791     tm->sec = (long) timeB.time;
13792     tm->ms = (int) timeB.millitm;
13793
13794 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13795     tm->sec = (long) time(NULL);
13796     tm->ms = 0;
13797 #endif
13798 #endif
13799 }
13800
13801 /* Return the difference in milliseconds between two
13802    time marks.  We assume the difference will fit in a long!
13803 */
13804 long
13805 SubtractTimeMarks(tm2, tm1)
13806      TimeMark *tm2, *tm1;
13807 {
13808     return 1000L*(tm2->sec - tm1->sec) +
13809            (long) (tm2->ms - tm1->ms);
13810 }
13811
13812
13813 /*
13814  * Code to manage the game clocks.
13815  *
13816  * In tournament play, black starts the clock and then white makes a move.
13817  * We give the human user a slight advantage if he is playing white---the
13818  * clocks don't run until he makes his first move, so it takes zero time.
13819  * Also, we don't account for network lag, so we could get out of sync
13820  * with GNU Chess's clock -- but then, referees are always right.  
13821  */
13822
13823 static TimeMark tickStartTM;
13824 static long intendedTickLength;
13825
13826 long
13827 NextTickLength(timeRemaining)
13828      long timeRemaining;
13829 {
13830     long nominalTickLength, nextTickLength;
13831
13832     if (timeRemaining > 0L && timeRemaining <= 10000L)
13833       nominalTickLength = 100L;
13834     else
13835       nominalTickLength = 1000L;
13836     nextTickLength = timeRemaining % nominalTickLength;
13837     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13838
13839     return nextTickLength;
13840 }
13841
13842 /* Adjust clock one minute up or down */
13843 void
13844 AdjustClock(Boolean which, int dir)
13845 {
13846     if(which) blackTimeRemaining += 60000*dir;
13847     else      whiteTimeRemaining += 60000*dir;
13848     DisplayBothClocks();
13849 }
13850
13851 /* Stop clocks and reset to a fresh time control */
13852 void
13853 ResetClocks() 
13854 {
13855     (void) StopClockTimer();
13856     if (appData.icsActive) {
13857         whiteTimeRemaining = blackTimeRemaining = 0;
13858     } else if (searchTime) {
13859         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13860         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13861     } else { /* [HGM] correct new time quote for time odds */
13862         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13863         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13864     }
13865     if (whiteFlag || blackFlag) {
13866         DisplayTitle("");
13867         whiteFlag = blackFlag = FALSE;
13868     }
13869     DisplayBothClocks();
13870 }
13871
13872 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13873
13874 /* Decrement running clock by amount of time that has passed */
13875 void
13876 DecrementClocks()
13877 {
13878     long timeRemaining;
13879     long lastTickLength, fudge;
13880     TimeMark now;
13881
13882     if (!appData.clockMode) return;
13883     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13884         
13885     GetTimeMark(&now);
13886
13887     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13888
13889     /* Fudge if we woke up a little too soon */
13890     fudge = intendedTickLength - lastTickLength;
13891     if (fudge < 0 || fudge > FUDGE) fudge = 0;
13892
13893     if (WhiteOnMove(forwardMostMove)) {
13894         if(whiteNPS >= 0) lastTickLength = 0;
13895         timeRemaining = whiteTimeRemaining -= lastTickLength;
13896         DisplayWhiteClock(whiteTimeRemaining - fudge,
13897                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13898     } else {
13899         if(blackNPS >= 0) lastTickLength = 0;
13900         timeRemaining = blackTimeRemaining -= lastTickLength;
13901         DisplayBlackClock(blackTimeRemaining - fudge,
13902                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13903     }
13904
13905     if (CheckFlags()) return;
13906         
13907     tickStartTM = now;
13908     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13909     StartClockTimer(intendedTickLength);
13910
13911     /* if the time remaining has fallen below the alarm threshold, sound the
13912      * alarm. if the alarm has sounded and (due to a takeback or time control
13913      * with increment) the time remaining has increased to a level above the
13914      * threshold, reset the alarm so it can sound again. 
13915      */
13916     
13917     if (appData.icsActive && appData.icsAlarm) {
13918
13919         /* make sure we are dealing with the user's clock */
13920         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13921                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13922            )) return;
13923
13924         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13925             alarmSounded = FALSE;
13926         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
13927             PlayAlarmSound();
13928             alarmSounded = TRUE;
13929         }
13930     }
13931 }
13932
13933
13934 /* A player has just moved, so stop the previously running
13935    clock and (if in clock mode) start the other one.
13936    We redisplay both clocks in case we're in ICS mode, because
13937    ICS gives us an update to both clocks after every move.
13938    Note that this routine is called *after* forwardMostMove
13939    is updated, so the last fractional tick must be subtracted
13940    from the color that is *not* on move now.
13941 */
13942 void
13943 SwitchClocks()
13944 {
13945     long lastTickLength;
13946     TimeMark now;
13947     int flagged = FALSE;
13948
13949     GetTimeMark(&now);
13950
13951     if (StopClockTimer() && appData.clockMode) {
13952         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13953         if (WhiteOnMove(forwardMostMove)) {
13954             if(blackNPS >= 0) lastTickLength = 0;
13955             blackTimeRemaining -= lastTickLength;
13956            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13957 //         if(pvInfoList[forwardMostMove-1].time == -1)
13958                  pvInfoList[forwardMostMove-1].time =               // use GUI time
13959                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13960         } else {
13961            if(whiteNPS >= 0) lastTickLength = 0;
13962            whiteTimeRemaining -= lastTickLength;
13963            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13964 //         if(pvInfoList[forwardMostMove-1].time == -1)
13965                  pvInfoList[forwardMostMove-1].time = 
13966                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13967         }
13968         flagged = CheckFlags();
13969     }
13970     CheckTimeControl();
13971
13972     if (flagged || !appData.clockMode) return;
13973
13974     switch (gameMode) {
13975       case MachinePlaysBlack:
13976       case MachinePlaysWhite:
13977       case BeginningOfGame:
13978         if (pausing) return;
13979         break;
13980
13981       case EditGame:
13982       case PlayFromGameFile:
13983       case IcsExamining:
13984         return;
13985
13986       default:
13987         break;
13988     }
13989
13990     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13991         if(WhiteOnMove(forwardMostMove))
13992              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13993         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13994     }
13995
13996     tickStartTM = now;
13997     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13998       whiteTimeRemaining : blackTimeRemaining);
13999     StartClockTimer(intendedTickLength);
14000 }
14001         
14002
14003 /* Stop both clocks */
14004 void
14005 StopClocks()
14006 {       
14007     long lastTickLength;
14008     TimeMark now;
14009
14010     if (!StopClockTimer()) return;
14011     if (!appData.clockMode) return;
14012
14013     GetTimeMark(&now);
14014
14015     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14016     if (WhiteOnMove(forwardMostMove)) {
14017         if(whiteNPS >= 0) lastTickLength = 0;
14018         whiteTimeRemaining -= lastTickLength;
14019         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14020     } else {
14021         if(blackNPS >= 0) lastTickLength = 0;
14022         blackTimeRemaining -= lastTickLength;
14023         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14024     }
14025     CheckFlags();
14026 }
14027         
14028 /* Start clock of player on move.  Time may have been reset, so
14029    if clock is already running, stop and restart it. */
14030 void
14031 StartClocks()
14032 {
14033     (void) StopClockTimer(); /* in case it was running already */
14034     DisplayBothClocks();
14035     if (CheckFlags()) return;
14036
14037     if (!appData.clockMode) return;
14038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14039
14040     GetTimeMark(&tickStartTM);
14041     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14042       whiteTimeRemaining : blackTimeRemaining);
14043
14044    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14045     whiteNPS = blackNPS = -1; 
14046     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14047        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14048         whiteNPS = first.nps;
14049     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14050        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14051         blackNPS = first.nps;
14052     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14053         whiteNPS = second.nps;
14054     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14055         blackNPS = second.nps;
14056     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14057
14058     StartClockTimer(intendedTickLength);
14059 }
14060
14061 char *
14062 TimeString(ms)
14063      long ms;
14064 {
14065     long second, minute, hour, day;
14066     char *sign = "";
14067     static char buf[32];
14068     
14069     if (ms > 0 && ms <= 9900) {
14070       /* convert milliseconds to tenths, rounding up */
14071       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14072
14073       sprintf(buf, " %03.1f ", tenths/10.0);
14074       return buf;
14075     }
14076
14077     /* convert milliseconds to seconds, rounding up */
14078     /* use floating point to avoid strangeness of integer division
14079        with negative dividends on many machines */
14080     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14081
14082     if (second < 0) {
14083         sign = "-";
14084         second = -second;
14085     }
14086     
14087     day = second / (60 * 60 * 24);
14088     second = second % (60 * 60 * 24);
14089     hour = second / (60 * 60);
14090     second = second % (60 * 60);
14091     minute = second / 60;
14092     second = second % 60;
14093     
14094     if (day > 0)
14095       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14096               sign, day, hour, minute, second);
14097     else if (hour > 0)
14098       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14099     else
14100       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14101     
14102     return buf;
14103 }
14104
14105
14106 /*
14107  * This is necessary because some C libraries aren't ANSI C compliant yet.
14108  */
14109 char *
14110 StrStr(string, match)
14111      char *string, *match;
14112 {
14113     int i, length;
14114     
14115     length = strlen(match);
14116     
14117     for (i = strlen(string) - length; i >= 0; i--, string++)
14118       if (!strncmp(match, string, length))
14119         return string;
14120     
14121     return NULL;
14122 }
14123
14124 char *
14125 StrCaseStr(string, match)
14126      char *string, *match;
14127 {
14128     int i, j, length;
14129     
14130     length = strlen(match);
14131     
14132     for (i = strlen(string) - length; i >= 0; i--, string++) {
14133         for (j = 0; j < length; j++) {
14134             if (ToLower(match[j]) != ToLower(string[j]))
14135               break;
14136         }
14137         if (j == length) return string;
14138     }
14139
14140     return NULL;
14141 }
14142
14143 #ifndef _amigados
14144 int
14145 StrCaseCmp(s1, s2)
14146      char *s1, *s2;
14147 {
14148     char c1, c2;
14149     
14150     for (;;) {
14151         c1 = ToLower(*s1++);
14152         c2 = ToLower(*s2++);
14153         if (c1 > c2) return 1;
14154         if (c1 < c2) return -1;
14155         if (c1 == NULLCHAR) return 0;
14156     }
14157 }
14158
14159
14160 int
14161 ToLower(c)
14162      int c;
14163 {
14164     return isupper(c) ? tolower(c) : c;
14165 }
14166
14167
14168 int
14169 ToUpper(c)
14170      int c;
14171 {
14172     return islower(c) ? toupper(c) : c;
14173 }
14174 #endif /* !_amigados    */
14175
14176 char *
14177 StrSave(s)
14178      char *s;
14179 {
14180     char *ret;
14181
14182     if ((ret = (char *) malloc(strlen(s) + 1))) {
14183         strcpy(ret, s);
14184     }
14185     return ret;
14186 }
14187
14188 char *
14189 StrSavePtr(s, savePtr)
14190      char *s, **savePtr;
14191 {
14192     if (*savePtr) {
14193         free(*savePtr);
14194     }
14195     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14196         strcpy(*savePtr, s);
14197     }
14198     return(*savePtr);
14199 }
14200
14201 char *
14202 PGNDate()
14203 {
14204     time_t clock;
14205     struct tm *tm;
14206     char buf[MSG_SIZ];
14207
14208     clock = time((time_t *)NULL);
14209     tm = localtime(&clock);
14210     sprintf(buf, "%04d.%02d.%02d",
14211             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14212     return StrSave(buf);
14213 }
14214
14215
14216 char *
14217 PositionToFEN(move, overrideCastling)
14218      int move;
14219      char *overrideCastling;
14220 {
14221     int i, j, fromX, fromY, toX, toY;
14222     int whiteToPlay;
14223     char buf[128];
14224     char *p, *q;
14225     int emptycount;
14226     ChessSquare piece;
14227
14228     whiteToPlay = (gameMode == EditPosition) ?
14229       !blackPlaysFirst : (move % 2 == 0);
14230     p = buf;
14231
14232     /* Piece placement data */
14233     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14234         emptycount = 0;
14235         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14236             if (boards[move][i][j] == EmptySquare) {
14237                 emptycount++;
14238             } else { ChessSquare piece = boards[move][i][j];
14239                 if (emptycount > 0) {
14240                     if(emptycount<10) /* [HGM] can be >= 10 */
14241                         *p++ = '0' + emptycount;
14242                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14243                     emptycount = 0;
14244                 }
14245                 if(PieceToChar(piece) == '+') {
14246                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14247                     *p++ = '+';
14248                     piece = (ChessSquare)(DEMOTED piece);
14249                 } 
14250                 *p++ = PieceToChar(piece);
14251                 if(p[-1] == '~') {
14252                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14253                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14254                     *p++ = '~';
14255                 }
14256             }
14257         }
14258         if (emptycount > 0) {
14259             if(emptycount<10) /* [HGM] can be >= 10 */
14260                 *p++ = '0' + emptycount;
14261             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14262             emptycount = 0;
14263         }
14264         *p++ = '/';
14265     }
14266     *(p - 1) = ' ';
14267
14268     /* [HGM] print Crazyhouse or Shogi holdings */
14269     if( gameInfo.holdingsWidth ) {
14270         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14271         q = p;
14272         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14273             piece = boards[move][i][BOARD_WIDTH-1];
14274             if( piece != EmptySquare )
14275               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14276                   *p++ = PieceToChar(piece);
14277         }
14278         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14279             piece = boards[move][BOARD_HEIGHT-i-1][0];
14280             if( piece != EmptySquare )
14281               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14282                   *p++ = PieceToChar(piece);
14283         }
14284
14285         if( q == p ) *p++ = '-';
14286         *p++ = ']';
14287         *p++ = ' ';
14288     }
14289
14290     /* Active color */
14291     *p++ = whiteToPlay ? 'w' : 'b';
14292     *p++ = ' ';
14293
14294   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14295     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14296   } else {
14297   if(nrCastlingRights) {
14298      q = p;
14299      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14300        /* [HGM] write directly from rights */
14301            if(boards[move][CASTLING][2] != NoRights &&
14302               boards[move][CASTLING][0] != NoRights   )
14303                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14304            if(boards[move][CASTLING][2] != NoRights &&
14305               boards[move][CASTLING][1] != NoRights   )
14306                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14307            if(boards[move][CASTLING][5] != NoRights &&
14308               boards[move][CASTLING][3] != NoRights   )
14309                 *p++ = boards[move][CASTLING][3] + AAA;
14310            if(boards[move][CASTLING][5] != NoRights &&
14311               boards[move][CASTLING][4] != NoRights   )
14312                 *p++ = boards[move][CASTLING][4] + AAA;
14313      } else {
14314
14315         /* [HGM] write true castling rights */
14316         if( nrCastlingRights == 6 ) {
14317             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14318                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14319             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14320                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14321             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14322                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14323             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14324                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14325         }
14326      }
14327      if (q == p) *p++ = '-'; /* No castling rights */
14328      *p++ = ' ';
14329   }
14330
14331   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14332      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14333     /* En passant target square */
14334     if (move > backwardMostMove) {
14335         fromX = moveList[move - 1][0] - AAA;
14336         fromY = moveList[move - 1][1] - ONE;
14337         toX = moveList[move - 1][2] - AAA;
14338         toY = moveList[move - 1][3] - ONE;
14339         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14340             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14341             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14342             fromX == toX) {
14343             /* 2-square pawn move just happened */
14344             *p++ = toX + AAA;
14345             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14346         } else {
14347             *p++ = '-';
14348         }
14349     } else if(move == backwardMostMove) {
14350         // [HGM] perhaps we should always do it like this, and forget the above?
14351         if((signed char)boards[move][EP_STATUS] >= 0) {
14352             *p++ = boards[move][EP_STATUS] + AAA;
14353             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14354         } else {
14355             *p++ = '-';
14356         }
14357     } else {
14358         *p++ = '-';
14359     }
14360     *p++ = ' ';
14361   }
14362   }
14363
14364     /* [HGM] find reversible plies */
14365     {   int i = 0, j=move;
14366
14367         if (appData.debugMode) { int k;
14368             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14369             for(k=backwardMostMove; k<=forwardMostMove; k++)
14370                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14371
14372         }
14373
14374         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14375         if( j == backwardMostMove ) i += initialRulePlies;
14376         sprintf(p, "%d ", i);
14377         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14378     }
14379     /* Fullmove number */
14380     sprintf(p, "%d", (move / 2) + 1);
14381     
14382     return StrSave(buf);
14383 }
14384
14385 Boolean
14386 ParseFEN(board, blackPlaysFirst, fen)
14387     Board board;
14388      int *blackPlaysFirst;
14389      char *fen;
14390 {
14391     int i, j;
14392     char *p;
14393     int emptycount;
14394     ChessSquare piece;
14395
14396     p = fen;
14397
14398     /* [HGM] by default clear Crazyhouse holdings, if present */
14399     if(gameInfo.holdingsWidth) {
14400        for(i=0; i<BOARD_HEIGHT; i++) {
14401            board[i][0]             = EmptySquare; /* black holdings */
14402            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14403            board[i][1]             = (ChessSquare) 0; /* black counts */
14404            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14405        }
14406     }
14407
14408     /* Piece placement data */
14409     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14410         j = 0;
14411         for (;;) {
14412             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14413                 if (*p == '/') p++;
14414                 emptycount = gameInfo.boardWidth - j;
14415                 while (emptycount--)
14416                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14417                 break;
14418 #if(BOARD_FILES >= 10)
14419             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14420                 p++; emptycount=10;
14421                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14422                 while (emptycount--)
14423                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14424 #endif
14425             } else if (isdigit(*p)) {
14426                 emptycount = *p++ - '0';
14427                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14428                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14429                 while (emptycount--)
14430                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14431             } else if (*p == '+' || isalpha(*p)) {
14432                 if (j >= gameInfo.boardWidth) return FALSE;
14433                 if(*p=='+') {
14434                     piece = CharToPiece(*++p);
14435                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14436                     piece = (ChessSquare) (PROMOTED piece ); p++;
14437                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14438                 } else piece = CharToPiece(*p++);
14439
14440                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14441                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14442                     piece = (ChessSquare) (PROMOTED piece);
14443                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14444                     p++;
14445                 }
14446                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14447             } else {
14448                 return FALSE;
14449             }
14450         }
14451     }
14452     while (*p == '/' || *p == ' ') p++;
14453
14454     /* [HGM] look for Crazyhouse holdings here */
14455     while(*p==' ') p++;
14456     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14457         if(*p == '[') p++;
14458         if(*p == '-' ) *p++; /* empty holdings */ else {
14459             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14460             /* if we would allow FEN reading to set board size, we would   */
14461             /* have to add holdings and shift the board read so far here   */
14462             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14463                 *p++;
14464                 if((int) piece >= (int) BlackPawn ) {
14465                     i = (int)piece - (int)BlackPawn;
14466                     i = PieceToNumber((ChessSquare)i);
14467                     if( i >= gameInfo.holdingsSize ) return FALSE;
14468                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14469                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14470                 } else {
14471                     i = (int)piece - (int)WhitePawn;
14472                     i = PieceToNumber((ChessSquare)i);
14473                     if( i >= gameInfo.holdingsSize ) return FALSE;
14474                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14475                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14476                 }
14477             }
14478         }
14479         if(*p == ']') *p++;
14480     }
14481
14482     while(*p == ' ') p++;
14483
14484     /* Active color */
14485     switch (*p++) {
14486       case 'w':
14487         *blackPlaysFirst = FALSE;
14488         break;
14489       case 'b': 
14490         *blackPlaysFirst = TRUE;
14491         break;
14492       default:
14493         return FALSE;
14494     }
14495
14496     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14497     /* return the extra info in global variiables             */
14498
14499     /* set defaults in case FEN is incomplete */
14500     board[EP_STATUS] = EP_UNKNOWN;
14501     for(i=0; i<nrCastlingRights; i++ ) {
14502         board[CASTLING][i] =
14503             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14504     }   /* assume possible unless obviously impossible */
14505     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14506     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14507     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14508                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14509     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14510     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14511     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14512                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14513     FENrulePlies = 0;
14514
14515     while(*p==' ') p++;
14516     if(nrCastlingRights) {
14517       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14518           /* castling indicator present, so default becomes no castlings */
14519           for(i=0; i<nrCastlingRights; i++ ) {
14520                  board[CASTLING][i] = NoRights;
14521           }
14522       }
14523       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14524              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14525              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14526              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14527         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14528
14529         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14530             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14531             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14532         }
14533         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14534             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14535         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14536                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14537         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14538                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14539         switch(c) {
14540           case'K':
14541               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14542               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14543               board[CASTLING][2] = whiteKingFile;
14544               break;
14545           case'Q':
14546               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14547               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14548               board[CASTLING][2] = whiteKingFile;
14549               break;
14550           case'k':
14551               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14552               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14553               board[CASTLING][5] = blackKingFile;
14554               break;
14555           case'q':
14556               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14557               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14558               board[CASTLING][5] = blackKingFile;
14559           case '-':
14560               break;
14561           default: /* FRC castlings */
14562               if(c >= 'a') { /* black rights */
14563                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14564                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14565                   if(i == BOARD_RGHT) break;
14566                   board[CASTLING][5] = i;
14567                   c -= AAA;
14568                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14569                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14570                   if(c > i)
14571                       board[CASTLING][3] = c;
14572                   else
14573                       board[CASTLING][4] = c;
14574               } else { /* white rights */
14575                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14576                     if(board[0][i] == WhiteKing) break;
14577                   if(i == BOARD_RGHT) break;
14578                   board[CASTLING][2] = i;
14579                   c -= AAA - 'a' + 'A';
14580                   if(board[0][c] >= WhiteKing) break;
14581                   if(c > i)
14582                       board[CASTLING][0] = c;
14583                   else
14584                       board[CASTLING][1] = c;
14585               }
14586         }
14587       }
14588       for(i=0; i<nrCastlingRights; i++)
14589         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14590     if (appData.debugMode) {
14591         fprintf(debugFP, "FEN castling rights:");
14592         for(i=0; i<nrCastlingRights; i++)
14593         fprintf(debugFP, " %d", board[CASTLING][i]);
14594         fprintf(debugFP, "\n");
14595     }
14596
14597       while(*p==' ') p++;
14598     }
14599
14600     /* read e.p. field in games that know e.p. capture */
14601     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14602        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14603       if(*p=='-') {
14604         p++; board[EP_STATUS] = EP_NONE;
14605       } else {
14606          char c = *p++ - AAA;
14607
14608          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14609          if(*p >= '0' && *p <='9') *p++;
14610          board[EP_STATUS] = c;
14611       }
14612     }
14613
14614
14615     if(sscanf(p, "%d", &i) == 1) {
14616         FENrulePlies = i; /* 50-move ply counter */
14617         /* (The move number is still ignored)    */
14618     }
14619
14620     return TRUE;
14621 }
14622       
14623 void
14624 EditPositionPasteFEN(char *fen)
14625 {
14626   if (fen != NULL) {
14627     Board initial_position;
14628
14629     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14630       DisplayError(_("Bad FEN position in clipboard"), 0);
14631       return ;
14632     } else {
14633       int savedBlackPlaysFirst = blackPlaysFirst;
14634       EditPositionEvent();
14635       blackPlaysFirst = savedBlackPlaysFirst;
14636       CopyBoard(boards[0], initial_position);
14637       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14638       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14639       DisplayBothClocks();
14640       DrawPosition(FALSE, boards[currentMove]);
14641     }
14642   }
14643 }
14644
14645 static char cseq[12] = "\\   ";
14646
14647 Boolean set_cont_sequence(char *new_seq)
14648 {
14649     int len;
14650     Boolean ret;
14651
14652     // handle bad attempts to set the sequence
14653         if (!new_seq)
14654                 return 0; // acceptable error - no debug
14655
14656     len = strlen(new_seq);
14657     ret = (len > 0) && (len < sizeof(cseq));
14658     if (ret)
14659         strcpy(cseq, new_seq);
14660     else if (appData.debugMode)
14661         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14662     return ret;
14663 }
14664
14665 /*
14666     reformat a source message so words don't cross the width boundary.  internal
14667     newlines are not removed.  returns the wrapped size (no null character unless
14668     included in source message).  If dest is NULL, only calculate the size required
14669     for the dest buffer.  lp argument indicats line position upon entry, and it's
14670     passed back upon exit.
14671 */
14672 int wrap(char *dest, char *src, int count, int width, int *lp)
14673 {
14674     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14675
14676     cseq_len = strlen(cseq);
14677     old_line = line = *lp;
14678     ansi = len = clen = 0;
14679
14680     for (i=0; i < count; i++)
14681     {
14682         if (src[i] == '\033')
14683             ansi = 1;
14684
14685         // if we hit the width, back up
14686         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14687         {
14688             // store i & len in case the word is too long
14689             old_i = i, old_len = len;
14690
14691             // find the end of the last word
14692             while (i && src[i] != ' ' && src[i] != '\n')
14693             {
14694                 i--;
14695                 len--;
14696             }
14697
14698             // word too long?  restore i & len before splitting it
14699             if ((old_i-i+clen) >= width)
14700             {
14701                 i = old_i;
14702                 len = old_len;
14703             }
14704
14705             // extra space?
14706             if (i && src[i-1] == ' ')
14707                 len--;
14708
14709             if (src[i] != ' ' && src[i] != '\n')
14710             {
14711                 i--;
14712                 if (len)
14713                     len--;
14714             }
14715
14716             // now append the newline and continuation sequence
14717             if (dest)
14718                 dest[len] = '\n';
14719             len++;
14720             if (dest)
14721                 strncpy(dest+len, cseq, cseq_len);
14722             len += cseq_len;
14723             line = cseq_len;
14724             clen = cseq_len;
14725             continue;
14726         }
14727
14728         if (dest)
14729             dest[len] = src[i];
14730         len++;
14731         if (!ansi)
14732             line++;
14733         if (src[i] == '\n')
14734             line = 0;
14735         if (src[i] == 'm')
14736             ansi = 0;
14737     }
14738     if (dest && appData.debugMode)
14739     {
14740         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14741             count, width, line, len, *lp);
14742         show_bytes(debugFP, src, count);
14743         fprintf(debugFP, "\ndest: ");
14744         show_bytes(debugFP, dest, len);
14745         fprintf(debugFP, "\n");
14746     }
14747     *lp = dest ? line : old_line;
14748
14749     return len;
14750 }
14751
14752 // [HGM] vari: routines for shelving variations
14753
14754 void 
14755 PushTail(int firstMove, int lastMove)
14756 {
14757         int i, j, nrMoves = lastMove - firstMove;
14758
14759         if(appData.icsActive) { // only in local mode
14760                 forwardMostMove = currentMove; // mimic old ICS behavior
14761                 return;
14762         }
14763         if(storedGames >= MAX_VARIATIONS-1) return;
14764
14765         // push current tail of game on stack
14766         savedResult[storedGames] = gameInfo.result;
14767         savedDetails[storedGames] = gameInfo.resultDetails;
14768         gameInfo.resultDetails = NULL;
14769         savedFirst[storedGames] = firstMove;
14770         savedLast [storedGames] = lastMove;
14771         savedFramePtr[storedGames] = framePtr;
14772         framePtr -= nrMoves; // reserve space for the boards
14773         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14774             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14775             for(j=0; j<MOVE_LEN; j++)
14776                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14777             for(j=0; j<2*MOVE_LEN; j++)
14778                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14779             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14780             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14781             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14782             pvInfoList[firstMove+i-1].depth = 0;
14783             commentList[framePtr+i] = commentList[firstMove+i];
14784             commentList[firstMove+i] = NULL;
14785         }
14786
14787         storedGames++;
14788         forwardMostMove = currentMove; // truncte game so we can start variation
14789         if(storedGames == 1) GreyRevert(FALSE);
14790 }
14791
14792 Boolean
14793 PopTail(Boolean annotate)
14794 {
14795         int i, j, nrMoves;
14796         char buf[8000], moveBuf[20];
14797
14798         if(appData.icsActive) return FALSE; // only in local mode
14799         if(!storedGames) return FALSE; // sanity
14800
14801         storedGames--;
14802         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14803         nrMoves = savedLast[storedGames] - currentMove;
14804         if(annotate) {
14805                 int cnt = 10;
14806                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14807                 else strcpy(buf, "(");
14808                 for(i=currentMove; i<forwardMostMove; i++) {
14809                         if(WhiteOnMove(i))
14810                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14811                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14812                         strcat(buf, moveBuf);
14813                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14814                 }
14815                 strcat(buf, ")");
14816         }
14817         for(i=1; i<nrMoves; i++) { // copy last variation back
14818             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14819             for(j=0; j<MOVE_LEN; j++)
14820                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14821             for(j=0; j<2*MOVE_LEN; j++)
14822                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14823             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14824             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14825             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14826             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14827             commentList[currentMove+i] = commentList[framePtr+i];
14828             commentList[framePtr+i] = NULL;
14829         }
14830         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14831         framePtr = savedFramePtr[storedGames];
14832         gameInfo.result = savedResult[storedGames];
14833         if(gameInfo.resultDetails != NULL) {
14834             free(gameInfo.resultDetails);
14835       }
14836         gameInfo.resultDetails = savedDetails[storedGames];
14837         forwardMostMove = currentMove + nrMoves;
14838         if(storedGames == 0) GreyRevert(TRUE);
14839         return TRUE;
14840 }
14841
14842 void 
14843 CleanupTail()
14844 {       // remove all shelved variations
14845         int i;
14846         for(i=0; i<storedGames; i++) {
14847             if(savedDetails[i])
14848                 free(savedDetails[i]);
14849             savedDetails[i] = NULL;
14850         }
14851         for(i=framePtr; i<MAX_MOVES; i++) {
14852                 if(commentList[i]) free(commentList[i]);
14853                 commentList[i] = NULL;
14854         }
14855         framePtr = MAX_MOVES-1;
14856         storedGames = 0;
14857 }