Handle display of PV that starts with other move than played
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2744                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2745                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2746                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2747                 int p;
2748                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2749                 chattingPartner = -1;
2750
2751                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2752                 for(p=0; p<MAX_CHAT; p++) {
2753                     if(channel == atoi(chatPartner[p])) {
2754                     talker[0] = '['; strcat(talker, "] ");
2755                     chattingPartner = p; break;
2756                     }
2757                 } else
2758                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2759                 for(p=0; p<MAX_CHAT; p++) {
2760                     if(!strcmp("WHISPER", chatPartner[p])) {
2761                         talker[0] = '['; strcat(talker, "] ");
2762                         chattingPartner = p; break;
2763                     }
2764                 }
2765                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2766                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2767                     talker[0] = 0;
2768                     chattingPartner = p; break;
2769                 }
2770                 if(chattingPartner<0) i = oldi; else {
2771                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2772                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773                     started = STARTED_COMMENT;
2774                     parse_pos = 0; parse[0] = NULLCHAR;
2775                     savingComment = 3 + chattingPartner; // counts as TRUE
2776                     suppressKibitz = TRUE;
2777                     continue;
2778                 }
2779             } // [HGM] chat: end of patch
2780
2781             if (appData.zippyTalk || appData.zippyPlay) {
2782                 /* [DM] Backup address for color zippy lines */
2783                 backup = i;
2784 #if ZIPPY
2785        #ifdef WIN32
2786                if (loggedOn == TRUE)
2787                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2788                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2789        #else
2790                 if (ZippyControl(buf, &i) ||
2791                     ZippyConverse(buf, &i) ||
2792                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2793                       loggedOn = TRUE;
2794                       if (!appData.colorize) continue;
2795                 }
2796        #endif
2797 #endif
2798             } // [DM] 'else { ' deleted
2799                 if (
2800                     /* Regular tells and says */
2801                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2802                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2803                     looking_at(buf, &i, "* says: ") ||
2804                     /* Don't color "message" or "messages" output */
2805                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2806                     looking_at(buf, &i, "*. * at *:*: ") ||
2807                     looking_at(buf, &i, "--* (*:*): ") ||
2808                     /* Message notifications (same color as tells) */
2809                     looking_at(buf, &i, "* has left a message ") ||
2810                     looking_at(buf, &i, "* just sent you a message:\n") ||
2811                     /* Whispers and kibitzes */
2812                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2813                     looking_at(buf, &i, "* kibitzes: ") ||
2814                     /* Channel tells */
2815                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2816
2817                   if (tkind == 1 && strchr(star_match[0], ':')) {
2818                       /* Avoid "tells you:" spoofs in channels */
2819                      tkind = 3;
2820                   }
2821                   if (star_match[0][0] == NULLCHAR ||
2822                       strchr(star_match[0], ' ') ||
2823                       (tkind == 3 && strchr(star_match[1], ' '))) {
2824                     /* Reject bogus matches */
2825                     i = oldi;
2826                   } else {
2827                     if (appData.colorize) {
2828                       if (oldi > next_out) {
2829                         SendToPlayer(&buf[next_out], oldi - next_out);
2830                         next_out = oldi;
2831                       }
2832                       switch (tkind) {
2833                       case 1:
2834                         Colorize(ColorTell, FALSE);
2835                         curColor = ColorTell;
2836                         break;
2837                       case 2:
2838                         Colorize(ColorKibitz, FALSE);
2839                         curColor = ColorKibitz;
2840                         break;
2841                       case 3:
2842                         p = strrchr(star_match[1], '(');
2843                         if (p == NULL) {
2844                           p = star_match[1];
2845                         } else {
2846                           p++;
2847                         }
2848                         if (atoi(p) == 1) {
2849                           Colorize(ColorChannel1, FALSE);
2850                           curColor = ColorChannel1;
2851                         } else {
2852                           Colorize(ColorChannel, FALSE);
2853                           curColor = ColorChannel;
2854                         }
2855                         break;
2856                       case 5:
2857                         curColor = ColorNormal;
2858                         break;
2859                       }
2860                     }
2861                     if (started == STARTED_NONE && appData.autoComment &&
2862                         (gameMode == IcsObserving ||
2863                          gameMode == IcsPlayingWhite ||
2864                          gameMode == IcsPlayingBlack)) {
2865                       parse_pos = i - oldi;
2866                       memcpy(parse, &buf[oldi], parse_pos);
2867                       parse[parse_pos] = NULLCHAR;
2868                       started = STARTED_COMMENT;
2869                       savingComment = TRUE;
2870                     } else {
2871                       started = STARTED_CHATTER;
2872                       savingComment = FALSE;
2873                     }
2874                     loggedOn = TRUE;
2875                     continue;
2876                   }
2877                 }
2878
2879                 if (looking_at(buf, &i, "* s-shouts: ") ||
2880                     looking_at(buf, &i, "* c-shouts: ")) {
2881                     if (appData.colorize) {
2882                         if (oldi > next_out) {
2883                             SendToPlayer(&buf[next_out], oldi - next_out);
2884                             next_out = oldi;
2885                         }
2886                         Colorize(ColorSShout, FALSE);
2887                         curColor = ColorSShout;
2888                     }
2889                     loggedOn = TRUE;
2890                     started = STARTED_CHATTER;
2891                     continue;
2892                 }
2893
2894                 if (looking_at(buf, &i, "--->")) {
2895                     loggedOn = TRUE;
2896                     continue;
2897                 }
2898
2899                 if (looking_at(buf, &i, "* shouts: ") ||
2900                     looking_at(buf, &i, "--> ")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorShout, FALSE);
2907                         curColor = ColorShout;
2908                     }
2909                     loggedOn = TRUE;
2910                     started = STARTED_CHATTER;
2911                     continue;
2912                 }
2913
2914                 if (looking_at( buf, &i, "Challenge:")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorChallenge, FALSE);
2921                         curColor = ColorChallenge;
2922                     }
2923                     loggedOn = TRUE;
2924                     continue;
2925                 }
2926
2927                 if (looking_at(buf, &i, "* offers you") ||
2928                     looking_at(buf, &i, "* offers to be") ||
2929                     looking_at(buf, &i, "* would like to") ||
2930                     looking_at(buf, &i, "* requests to") ||
2931                     looking_at(buf, &i, "Your opponent offers") ||
2932                     looking_at(buf, &i, "Your opponent requests")) {
2933
2934                     if (appData.colorize) {
2935                         if (oldi > next_out) {
2936                             SendToPlayer(&buf[next_out], oldi - next_out);
2937                             next_out = oldi;
2938                         }
2939                         Colorize(ColorRequest, FALSE);
2940                         curColor = ColorRequest;
2941                     }
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "* (*) seeking")) {
2946                     if (appData.colorize) {
2947                         if (oldi > next_out) {
2948                             SendToPlayer(&buf[next_out], oldi - next_out);
2949                             next_out = oldi;
2950                         }
2951                         Colorize(ColorSeek, FALSE);
2952                         curColor = ColorSeek;
2953                     }
2954                     continue;
2955             }
2956
2957             if (looking_at(buf, &i, "\\   ")) {
2958                 if (prevColor != ColorNormal) {
2959                     if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                     }
2963                     Colorize(prevColor, TRUE);
2964                     curColor = prevColor;
2965                 }
2966                 if (savingComment) {
2967                     parse_pos = i - oldi;
2968                     memcpy(parse, &buf[oldi], parse_pos);
2969                     parse[parse_pos] = NULLCHAR;
2970                     started = STARTED_COMMENT;
2971                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2972                         chattingPartner = savingComment - 3; // kludge to remember the box
2973                 } else {
2974                     started = STARTED_CHATTER;
2975                 }
2976                 continue;
2977             }
2978
2979             if (looking_at(buf, &i, "Black Strength :") ||
2980                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2981                 looking_at(buf, &i, "<10>") ||
2982                 looking_at(buf, &i, "#@#")) {
2983                 /* Wrong board style */
2984                 loggedOn = TRUE;
2985                 SendToICS(ics_prefix);
2986                 SendToICS("set style 12\n");
2987                 SendToICS(ics_prefix);
2988                 SendToICS("refresh\n");
2989                 continue;
2990             }
2991             
2992             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2993                 ICSInitScript();
2994                 have_sent_ICS_logon = 1;
2995                 continue;
2996             }
2997               
2998             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2999                 (looking_at(buf, &i, "\n<12> ") ||
3000                  looking_at(buf, &i, "<12> "))) {
3001                 loggedOn = TRUE;
3002                 if (oldi > next_out) {
3003                     SendToPlayer(&buf[next_out], oldi - next_out);
3004                 }
3005                 next_out = i;
3006                 started = STARTED_BOARD;
3007                 parse_pos = 0;
3008                 continue;
3009             }
3010
3011             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3012                 looking_at(buf, &i, "<b1> ")) {
3013                 if (oldi > next_out) {
3014                     SendToPlayer(&buf[next_out], oldi - next_out);
3015                 }
3016                 next_out = i;
3017                 started = STARTED_HOLDINGS;
3018                 parse_pos = 0;
3019                 continue;
3020             }
3021
3022             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3023                 loggedOn = TRUE;
3024                 /* Header for a move list -- first line */
3025
3026                 switch (ics_getting_history) {
3027                   case H_FALSE:
3028                     switch (gameMode) {
3029                       case IcsIdle:
3030                       case BeginningOfGame:
3031                         /* User typed "moves" or "oldmoves" while we
3032                            were idle.  Pretend we asked for these
3033                            moves and soak them up so user can step
3034                            through them and/or save them.
3035                            */
3036                         Reset(FALSE, TRUE);
3037                         gameMode = IcsObserving;
3038                         ModeHighlight();
3039                         ics_gamenum = -1;
3040                         ics_getting_history = H_GOT_UNREQ_HEADER;
3041                         break;
3042                       case EditGame: /*?*/
3043                       case EditPosition: /*?*/
3044                         /* Should above feature work in these modes too? */
3045                         /* For now it doesn't */
3046                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3047                         break;
3048                       default:
3049                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3050                         break;
3051                     }
3052                     break;
3053                   case H_REQUESTED:
3054                     /* Is this the right one? */
3055                     if (gameInfo.white && gameInfo.black &&
3056                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3057                         strcmp(gameInfo.black, star_match[2]) == 0) {
3058                         /* All is well */
3059                         ics_getting_history = H_GOT_REQ_HEADER;
3060                     }
3061                     break;
3062                   case H_GOT_REQ_HEADER:
3063                   case H_GOT_UNREQ_HEADER:
3064                   case H_GOT_UNWANTED_HEADER:
3065                   case H_GETTING_MOVES:
3066                     /* Should not happen */
3067                     DisplayError(_("Error gathering move list: two headers"), 0);
3068                     ics_getting_history = H_FALSE;
3069                     break;
3070                 }
3071
3072                 /* Save player ratings into gameInfo if needed */
3073                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3074                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3075                     (gameInfo.whiteRating == -1 ||
3076                      gameInfo.blackRating == -1)) {
3077
3078                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3079                     gameInfo.blackRating = string_to_rating(star_match[3]);
3080                     if (appData.debugMode)
3081                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3082                               gameInfo.whiteRating, gameInfo.blackRating);
3083                 }
3084                 continue;
3085             }
3086
3087             if (looking_at(buf, &i,
3088               "* * match, initial time: * minute*, increment: * second")) {
3089                 /* Header for a move list -- second line */
3090                 /* Initial board will follow if this is a wild game */
3091                 if (gameInfo.event != NULL) free(gameInfo.event);
3092                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3093                 gameInfo.event = StrSave(str);
3094                 /* [HGM] we switched variant. Translate boards if needed. */
3095                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i, "Move  ")) {
3100                 /* Beginning of a move list */
3101                 switch (ics_getting_history) {
3102                   case H_FALSE:
3103                     /* Normally should not happen */
3104                     /* Maybe user hit reset while we were parsing */
3105                     break;
3106                   case H_REQUESTED:
3107                     /* Happens if we are ignoring a move list that is not
3108                      * the one we just requested.  Common if the user
3109                      * tries to observe two games without turning off
3110                      * getMoveList */
3111                     break;
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: nested"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                   case H_GOT_REQ_HEADER:
3118                     ics_getting_history = H_GETTING_MOVES;
3119                     started = STARTED_MOVES;
3120                     parse_pos = 0;
3121                     if (oldi > next_out) {
3122                         SendToPlayer(&buf[next_out], oldi - next_out);
3123                     }
3124                     break;
3125                   case H_GOT_UNREQ_HEADER:
3126                     ics_getting_history = H_GETTING_MOVES;
3127                     started = STARTED_MOVES_NOHIDE;
3128                     parse_pos = 0;
3129                     break;
3130                   case H_GOT_UNWANTED_HEADER:
3131                     ics_getting_history = H_FALSE;
3132                     break;
3133                 }
3134                 continue;
3135             }                           
3136             
3137             if (looking_at(buf, &i, "% ") ||
3138                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3139                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3140                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3141                     soughtPending = FALSE;
3142                     seekGraphUp = TRUE;
3143                     DrawSeekGraph();
3144                 }
3145                 if(suppressKibitz) next_out = i;
3146                 savingComment = FALSE;
3147                 suppressKibitz = 0;
3148                 switch (started) {
3149                   case STARTED_MOVES:
3150                   case STARTED_MOVES_NOHIDE:
3151                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3152                     parse[parse_pos + i - oldi] = NULLCHAR;
3153                     ParseGameHistory(parse);
3154 #if ZIPPY
3155                     if (appData.zippyPlay && first.initDone) {
3156                         FeedMovesToProgram(&first, forwardMostMove);
3157                         if (gameMode == IcsPlayingWhite) {
3158                             if (WhiteOnMove(forwardMostMove)) {
3159                                 if (first.sendTime) {
3160                                   if (first.useColors) {
3161                                     SendToProgram("black\n", &first); 
3162                                   }
3163                                   SendTimeRemaining(&first, TRUE);
3164                                 }
3165                                 if (first.useColors) {
3166                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3167                                 }
3168                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3169                                 first.maybeThinking = TRUE;
3170                             } else {
3171                                 if (first.usePlayother) {
3172                                   if (first.sendTime) {
3173                                     SendTimeRemaining(&first, TRUE);
3174                                   }
3175                                   SendToProgram("playother\n", &first);
3176                                   firstMove = FALSE;
3177                                 } else {
3178                                   firstMove = TRUE;
3179                                 }
3180                             }
3181                         } else if (gameMode == IcsPlayingBlack) {
3182                             if (!WhiteOnMove(forwardMostMove)) {
3183                                 if (first.sendTime) {
3184                                   if (first.useColors) {
3185                                     SendToProgram("white\n", &first);
3186                                   }
3187                                   SendTimeRemaining(&first, FALSE);
3188                                 }
3189                                 if (first.useColors) {
3190                                   SendToProgram("black\n", &first);
3191                                 }
3192                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3193                                 first.maybeThinking = TRUE;
3194                             } else {
3195                                 if (first.usePlayother) {
3196                                   if (first.sendTime) {
3197                                     SendTimeRemaining(&first, FALSE);
3198                                   }
3199                                   SendToProgram("playother\n", &first);
3200                                   firstMove = FALSE;
3201                                 } else {
3202                                   firstMove = TRUE;
3203                                 }
3204                             }
3205                         }                       
3206                     }
3207 #endif
3208                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3209                         /* Moves came from oldmoves or moves command
3210                            while we weren't doing anything else.
3211                            */
3212                         currentMove = forwardMostMove;
3213                         ClearHighlights();/*!!could figure this out*/
3214                         flipView = appData.flipView;
3215                         DrawPosition(TRUE, boards[currentMove]);
3216                         DisplayBothClocks();
3217                         sprintf(str, "%s vs. %s",
3218                                 gameInfo.white, gameInfo.black);
3219                         DisplayTitle(str);
3220                         gameMode = IcsIdle;
3221                     } else {
3222                         /* Moves were history of an active game */
3223                         if (gameInfo.resultDetails != NULL) {
3224                             free(gameInfo.resultDetails);
3225                             gameInfo.resultDetails = NULL;
3226                         }
3227                     }
3228                     HistorySet(parseList, backwardMostMove,
3229                                forwardMostMove, currentMove-1);
3230                     DisplayMove(currentMove - 1);
3231                     if (started == STARTED_MOVES) next_out = i;
3232                     started = STARTED_NONE;
3233                     ics_getting_history = H_FALSE;
3234                     break;
3235
3236                   case STARTED_OBSERVE:
3237                     started = STARTED_NONE;
3238                     SendToICS(ics_prefix);
3239                     SendToICS("refresh\n");
3240                     break;
3241
3242                   default:
3243                     break;
3244                 }
3245                 if(bookHit) { // [HGM] book: simulate book reply
3246                     static char bookMove[MSG_SIZ]; // a bit generous?
3247
3248                     programStats.nodes = programStats.depth = programStats.time = 
3249                     programStats.score = programStats.got_only_move = 0;
3250                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3251
3252                     strcpy(bookMove, "move ");
3253                     strcat(bookMove, bookHit);
3254                     HandleMachineMove(bookMove, &first);
3255                 }
3256                 continue;
3257             }
3258             
3259             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3260                  started == STARTED_HOLDINGS ||
3261                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3262                 /* Accumulate characters in move list or board */
3263                 parse[parse_pos++] = buf[i];
3264             }
3265             
3266             /* Start of game messages.  Mostly we detect start of game
3267                when the first board image arrives.  On some versions
3268                of the ICS, though, we need to do a "refresh" after starting
3269                to observe in order to get the current board right away. */
3270             if (looking_at(buf, &i, "Adding game * to observation list")) {
3271                 started = STARTED_OBSERVE;
3272                 continue;
3273             }
3274
3275             /* Handle auto-observe */
3276             if (appData.autoObserve &&
3277                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3278                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3279                 char *player;
3280                 /* Choose the player that was highlighted, if any. */
3281                 if (star_match[0][0] == '\033' ||
3282                     star_match[1][0] != '\033') {
3283                     player = star_match[0];
3284                 } else {
3285                     player = star_match[2];
3286                 }
3287                 sprintf(str, "%sobserve %s\n",
3288                         ics_prefix, StripHighlightAndTitle(player));
3289                 SendToICS(str);
3290
3291                 /* Save ratings from notify string */
3292                 strcpy(player1Name, star_match[0]);
3293                 player1Rating = string_to_rating(star_match[1]);
3294                 strcpy(player2Name, star_match[2]);
3295                 player2Rating = string_to_rating(star_match[3]);
3296
3297                 if (appData.debugMode)
3298                   fprintf(debugFP, 
3299                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3300                           player1Name, player1Rating,
3301                           player2Name, player2Rating);
3302
3303                 continue;
3304             }
3305
3306             /* Deal with automatic examine mode after a game,
3307                and with IcsObserving -> IcsExamining transition */
3308             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3309                 looking_at(buf, &i, "has made you an examiner of game *")) {
3310
3311                 int gamenum = atoi(star_match[0]);
3312                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3313                     gamenum == ics_gamenum) {
3314                     /* We were already playing or observing this game;
3315                        no need to refetch history */
3316                     gameMode = IcsExamining;
3317                     if (pausing) {
3318                         pauseExamForwardMostMove = forwardMostMove;
3319                     } else if (currentMove < forwardMostMove) {
3320                         ForwardInner(forwardMostMove);
3321                     }
3322                 } else {
3323                     /* I don't think this case really can happen */
3324                     SendToICS(ics_prefix);
3325                     SendToICS("refresh\n");
3326                 }
3327                 continue;
3328             }    
3329             
3330             /* Error messages */
3331 //          if (ics_user_moved) {
3332             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3333                 if (looking_at(buf, &i, "Illegal move") ||
3334                     looking_at(buf, &i, "Not a legal move") ||
3335                     looking_at(buf, &i, "Your king is in check") ||
3336                     looking_at(buf, &i, "It isn't your turn") ||
3337                     looking_at(buf, &i, "It is not your move")) {
3338                     /* Illegal move */
3339                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3340                         currentMove = forwardMostMove-1;
3341                         DisplayMove(currentMove - 1); /* before DMError */
3342                         DrawPosition(FALSE, boards[currentMove]);
3343                         SwitchClocks(forwardMostMove-1); // [HGM] race
3344                         DisplayBothClocks();
3345                     }
3346                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3347                     ics_user_moved = 0;
3348                     continue;
3349                 }
3350             }
3351
3352             if (looking_at(buf, &i, "still have time") ||
3353                 looking_at(buf, &i, "not out of time") ||
3354                 looking_at(buf, &i, "either player is out of time") ||
3355                 looking_at(buf, &i, "has timeseal; checking")) {
3356                 /* We must have called his flag a little too soon */
3357                 whiteFlag = blackFlag = FALSE;
3358                 continue;
3359             }
3360
3361             if (looking_at(buf, &i, "added * seconds to") ||
3362                 looking_at(buf, &i, "seconds were added to")) {
3363                 /* Update the clocks */
3364                 SendToICS(ics_prefix);
3365                 SendToICS("refresh\n");
3366                 continue;
3367             }
3368
3369             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3370                 ics_clock_paused = TRUE;
3371                 StopClocks();
3372                 continue;
3373             }
3374
3375             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3376                 ics_clock_paused = FALSE;
3377                 StartClocks();
3378                 continue;
3379             }
3380
3381             /* Grab player ratings from the Creating: message.
3382                Note we have to check for the special case when
3383                the ICS inserts things like [white] or [black]. */
3384             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3385                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3386                 /* star_matches:
3387                    0    player 1 name (not necessarily white)
3388                    1    player 1 rating
3389                    2    empty, white, or black (IGNORED)
3390                    3    player 2 name (not necessarily black)
3391                    4    player 2 rating
3392                    
3393                    The names/ratings are sorted out when the game
3394                    actually starts (below).
3395                 */
3396                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3397                 player1Rating = string_to_rating(star_match[1]);
3398                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3399                 player2Rating = string_to_rating(star_match[4]);
3400
3401                 if (appData.debugMode)
3402                   fprintf(debugFP, 
3403                           "Ratings from 'Creating:' %s %d, %s %d\n",
3404                           player1Name, player1Rating,
3405                           player2Name, player2Rating);
3406
3407                 continue;
3408             }
3409             
3410             /* Improved generic start/end-of-game messages */
3411             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3412                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3413                 /* If tkind == 0: */
3414                 /* star_match[0] is the game number */
3415                 /*           [1] is the white player's name */
3416                 /*           [2] is the black player's name */
3417                 /* For end-of-game: */
3418                 /*           [3] is the reason for the game end */
3419                 /*           [4] is a PGN end game-token, preceded by " " */
3420                 /* For start-of-game: */
3421                 /*           [3] begins with "Creating" or "Continuing" */
3422                 /*           [4] is " *" or empty (don't care). */
3423                 int gamenum = atoi(star_match[0]);
3424                 char *whitename, *blackname, *why, *endtoken;
3425                 ChessMove endtype = (ChessMove) 0;
3426
3427                 if (tkind == 0) {
3428                   whitename = star_match[1];
3429                   blackname = star_match[2];
3430                   why = star_match[3];
3431                   endtoken = star_match[4];
3432                 } else {
3433                   whitename = star_match[1];
3434                   blackname = star_match[3];
3435                   why = star_match[5];
3436                   endtoken = star_match[6];
3437                 }
3438
3439                 /* Game start messages */
3440                 if (strncmp(why, "Creating ", 9) == 0 ||
3441                     strncmp(why, "Continuing ", 11) == 0) {
3442                     gs_gamenum = gamenum;
3443                     strcpy(gs_kind, strchr(why, ' ') + 1);
3444                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3445 #if ZIPPY
3446                     if (appData.zippyPlay) {
3447                         ZippyGameStart(whitename, blackname);
3448                     }
3449 #endif /*ZIPPY*/
3450                     continue;
3451                 }
3452
3453                 /* Game end messages */
3454                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3455                     ics_gamenum != gamenum) {
3456                     continue;
3457                 }
3458                 while (endtoken[0] == ' ') endtoken++;
3459                 switch (endtoken[0]) {
3460                   case '*':
3461                   default:
3462                     endtype = GameUnfinished;
3463                     break;
3464                   case '0':
3465                     endtype = BlackWins;
3466                     break;
3467                   case '1':
3468                     if (endtoken[1] == '/')
3469                       endtype = GameIsDrawn;
3470                     else
3471                       endtype = WhiteWins;
3472                     break;
3473                 }
3474                 GameEnds(endtype, why, GE_ICS);
3475 #if ZIPPY
3476                 if (appData.zippyPlay && first.initDone) {
3477                     ZippyGameEnd(endtype, why);
3478                     if (first.pr == NULL) {
3479                       /* Start the next process early so that we'll
3480                          be ready for the next challenge */
3481                       StartChessProgram(&first);
3482                     }
3483                     /* Send "new" early, in case this command takes
3484                        a long time to finish, so that we'll be ready
3485                        for the next challenge. */
3486                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3487                     Reset(TRUE, TRUE);
3488                 }
3489 #endif /*ZIPPY*/
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "Removing game * from observation") ||
3494                 looking_at(buf, &i, "no longer observing game *") ||
3495                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3496                 if (gameMode == IcsObserving &&
3497                     atoi(star_match[0]) == ics_gamenum)
3498                   {
3499                       /* icsEngineAnalyze */
3500                       if (appData.icsEngineAnalyze) {
3501                             ExitAnalyzeMode();
3502                             ModeHighlight();
3503                       }
3504                       StopClocks();
3505                       gameMode = IcsIdle;
3506                       ics_gamenum = -1;
3507                       ics_user_moved = FALSE;
3508                   }
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "no longer examining game *")) {
3513                 if (gameMode == IcsExamining &&
3514                     atoi(star_match[0]) == ics_gamenum)
3515                   {
3516                       gameMode = IcsIdle;
3517                       ics_gamenum = -1;
3518                       ics_user_moved = FALSE;
3519                   }
3520                 continue;
3521             }
3522
3523             /* Advance leftover_start past any newlines we find,
3524                so only partial lines can get reparsed */
3525             if (looking_at(buf, &i, "\n")) {
3526                 prevColor = curColor;
3527                 if (curColor != ColorNormal) {
3528                     if (oldi > next_out) {
3529                         SendToPlayer(&buf[next_out], oldi - next_out);
3530                         next_out = oldi;
3531                     }
3532                     Colorize(ColorNormal, FALSE);
3533                     curColor = ColorNormal;
3534                 }
3535                 if (started == STARTED_BOARD) {
3536                     started = STARTED_NONE;
3537                     parse[parse_pos] = NULLCHAR;
3538                     ParseBoard12(parse);
3539                     ics_user_moved = 0;
3540
3541                     /* Send premove here */
3542                     if (appData.premove) {
3543                       char str[MSG_SIZ];
3544                       if (currentMove == 0 &&
3545                           gameMode == IcsPlayingWhite &&
3546                           appData.premoveWhite) {
3547                         sprintf(str, "%s\n", appData.premoveWhiteText);
3548                         if (appData.debugMode)
3549                           fprintf(debugFP, "Sending premove:\n");
3550                         SendToICS(str);
3551                       } else if (currentMove == 1 &&
3552                                  gameMode == IcsPlayingBlack &&
3553                                  appData.premoveBlack) {
3554                         sprintf(str, "%s\n", appData.premoveBlackText);
3555                         if (appData.debugMode)
3556                           fprintf(debugFP, "Sending premove:\n");
3557                         SendToICS(str);
3558                       } else if (gotPremove) {
3559                         gotPremove = 0;
3560                         ClearPremoveHighlights();
3561                         if (appData.debugMode)
3562                           fprintf(debugFP, "Sending premove:\n");
3563                           UserMoveEvent(premoveFromX, premoveFromY, 
3564                                         premoveToX, premoveToY, 
3565                                         premovePromoChar);
3566                       }
3567                     }
3568
3569                     /* Usually suppress following prompt */
3570                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3571                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3572                         if (looking_at(buf, &i, "*% ")) {
3573                             savingComment = FALSE;
3574                             suppressKibitz = 0;
3575                         }
3576                     }
3577                     next_out = i;
3578                 } else if (started == STARTED_HOLDINGS) {
3579                     int gamenum;
3580                     char new_piece[MSG_SIZ];
3581                     started = STARTED_NONE;
3582                     parse[parse_pos] = NULLCHAR;
3583                     if (appData.debugMode)
3584                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3585                                                         parse, currentMove);
3586                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3587                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3588                         if (gameInfo.variant == VariantNormal) {
3589                           /* [HGM] We seem to switch variant during a game!
3590                            * Presumably no holdings were displayed, so we have
3591                            * to move the position two files to the right to
3592                            * create room for them!
3593                            */
3594                           VariantClass newVariant;
3595                           switch(gameInfo.boardWidth) { // base guess on board width
3596                                 case 9:  newVariant = VariantShogi; break;
3597                                 case 10: newVariant = VariantGreat; break;
3598                                 default: newVariant = VariantCrazyhouse; break;
3599                           }
3600                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3601                           /* Get a move list just to see the header, which
3602                              will tell us whether this is really bug or zh */
3603                           if (ics_getting_history == H_FALSE) {
3604                             ics_getting_history = H_REQUESTED;
3605                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3606                             SendToICS(str);
3607                           }
3608                         }
3609                         new_piece[0] = NULLCHAR;
3610                         sscanf(parse, "game %d white [%s black [%s <- %s",
3611                                &gamenum, white_holding, black_holding,
3612                                new_piece);
3613                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3614                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3615                         /* [HGM] copy holdings to board holdings area */
3616                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3617                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3618                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3619 #if ZIPPY
3620                         if (appData.zippyPlay && first.initDone) {
3621                             ZippyHoldings(white_holding, black_holding,
3622                                           new_piece);
3623                         }
3624 #endif /*ZIPPY*/
3625                         if (tinyLayout || smallLayout) {
3626                             char wh[16], bh[16];
3627                             PackHolding(wh, white_holding);
3628                             PackHolding(bh, black_holding);
3629                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3630                                     gameInfo.white, gameInfo.black);
3631                         } else {
3632                             sprintf(str, "%s [%s] vs. %s [%s]",
3633                                     gameInfo.white, white_holding,
3634                                     gameInfo.black, black_holding);
3635                         }
3636
3637                         DrawPosition(FALSE, boards[currentMove]);
3638                         DisplayTitle(str);
3639                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3640                         sscanf(parse, "game %d white [%s black [%s <- %s",
3641                                &gamenum, white_holding, black_holding,
3642                                new_piece);
3643                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3644                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3645                         /* [HGM] copy holdings to partner-board holdings area */
3646                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3647                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3648                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3649                       }
3650                     }
3651                     /* Suppress following prompt */
3652                     if (looking_at(buf, &i, "*% ")) {
3653                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3654                         savingComment = FALSE;
3655                         suppressKibitz = 0;
3656                     }
3657                     next_out = i;
3658                 }
3659                 continue;
3660             }
3661
3662             i++;                /* skip unparsed character and loop back */
3663         }
3664         
3665         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3666 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3667 //          SendToPlayer(&buf[next_out], i - next_out);
3668             started != STARTED_HOLDINGS && leftover_start > next_out) {
3669             SendToPlayer(&buf[next_out], leftover_start - next_out);
3670             next_out = i;
3671         }
3672         
3673         leftover_len = buf_len - leftover_start;
3674         /* if buffer ends with something we couldn't parse,
3675            reparse it after appending the next read */
3676         
3677     } else if (count == 0) {
3678         RemoveInputSource(isr);
3679         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3680     } else {
3681         DisplayFatalError(_("Error reading from ICS"), error, 1);
3682     }
3683 }
3684
3685
3686 /* Board style 12 looks like this:
3687    
3688    <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
3689    
3690  * The "<12> " is stripped before it gets to this routine.  The two
3691  * trailing 0's (flip state and clock ticking) are later addition, and
3692  * some chess servers may not have them, or may have only the first.
3693  * Additional trailing fields may be added in the future.  
3694  */
3695
3696 #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"
3697
3698 #define RELATION_OBSERVING_PLAYED    0
3699 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3700 #define RELATION_PLAYING_MYMOVE      1
3701 #define RELATION_PLAYING_NOTMYMOVE  -1
3702 #define RELATION_EXAMINING           2
3703 #define RELATION_ISOLATED_BOARD     -3
3704 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3705
3706 void
3707 ParseBoard12(string)
3708      char *string;
3709
3710     GameMode newGameMode;
3711     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3712     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3713     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3714     char to_play, board_chars[200];
3715     char move_str[500], str[500], elapsed_time[500];
3716     char black[32], white[32];
3717     Board board;
3718     int prevMove = currentMove;
3719     int ticking = 2;
3720     ChessMove moveType;
3721     int fromX, fromY, toX, toY;
3722     char promoChar;
3723     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3724     char *bookHit = NULL; // [HGM] book
3725     Boolean weird = FALSE, reqFlag = FALSE;
3726
3727     fromX = fromY = toX = toY = -1;
3728     
3729     newGame = FALSE;
3730
3731     if (appData.debugMode)
3732       fprintf(debugFP, _("Parsing board: %s\n"), string);
3733
3734     move_str[0] = NULLCHAR;
3735     elapsed_time[0] = NULLCHAR;
3736     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3737         int  i = 0, j;
3738         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3739             if(string[i] == ' ') { ranks++; files = 0; }
3740             else files++;
3741             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3742             i++;
3743         }
3744         for(j = 0; j <i; j++) board_chars[j] = string[j];
3745         board_chars[i] = '\0';
3746         string += i + 1;
3747     }
3748     n = sscanf(string, PATTERN, &to_play, &double_push,
3749                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3750                &gamenum, white, black, &relation, &basetime, &increment,
3751                &white_stren, &black_stren, &white_time, &black_time,
3752                &moveNum, str, elapsed_time, move_str, &ics_flip,
3753                &ticking);
3754
3755     if (n < 21) {
3756         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3757         DisplayError(str, 0);
3758         return;
3759     }
3760
3761     /* Convert the move number to internal form */
3762     moveNum = (moveNum - 1) * 2;
3763     if (to_play == 'B') moveNum++;
3764     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3765       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3766                         0, 1);
3767       return;
3768     }
3769     
3770     switch (relation) {
3771       case RELATION_OBSERVING_PLAYED:
3772       case RELATION_OBSERVING_STATIC:
3773         if (gamenum == -1) {
3774             /* Old ICC buglet */
3775             relation = RELATION_OBSERVING_STATIC;
3776         }
3777         newGameMode = IcsObserving;
3778         break;
3779       case RELATION_PLAYING_MYMOVE:
3780       case RELATION_PLAYING_NOTMYMOVE:
3781         newGameMode =
3782           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3783             IcsPlayingWhite : IcsPlayingBlack;
3784         break;
3785       case RELATION_EXAMINING:
3786         newGameMode = IcsExamining;
3787         break;
3788       case RELATION_ISOLATED_BOARD:
3789       default:
3790         /* Just display this board.  If user was doing something else,
3791            we will forget about it until the next board comes. */ 
3792         newGameMode = IcsIdle;
3793         break;
3794       case RELATION_STARTING_POSITION:
3795         newGameMode = gameMode;
3796         break;
3797     }
3798     
3799     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3800          && newGameMode == IcsObserving && appData.bgObserve) {
3801       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3802       char buf[MSG_SIZ];
3803       for (k = 0; k < ranks; k++) {
3804         for (j = 0; j < files; j++)
3805           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3806         if(gameInfo.holdingsWidth > 1) {
3807              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3808              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3809         }
3810       }
3811       CopyBoard(partnerBoard, board);
3812       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3813       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3814                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3815       DisplayMessage(buf, "");
3816       return;
3817     }
3818
3819     /* Modify behavior for initial board display on move listing
3820        of wild games.
3821        */
3822     switch (ics_getting_history) {
3823       case H_FALSE:
3824       case H_REQUESTED:
3825         break;
3826       case H_GOT_REQ_HEADER:
3827       case H_GOT_UNREQ_HEADER:
3828         /* This is the initial position of the current game */
3829         gamenum = ics_gamenum;
3830         moveNum = 0;            /* old ICS bug workaround */
3831         if (to_play == 'B') {
3832           startedFromSetupPosition = TRUE;
3833           blackPlaysFirst = TRUE;
3834           moveNum = 1;
3835           if (forwardMostMove == 0) forwardMostMove = 1;
3836           if (backwardMostMove == 0) backwardMostMove = 1;
3837           if (currentMove == 0) currentMove = 1;
3838         }
3839         newGameMode = gameMode;
3840         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3841         break;
3842       case H_GOT_UNWANTED_HEADER:
3843         /* This is an initial board that we don't want */
3844         return;
3845       case H_GETTING_MOVES:
3846         /* Should not happen */
3847         DisplayError(_("Error gathering move list: extra board"), 0);
3848         ics_getting_history = H_FALSE;
3849         return;
3850     }
3851
3852    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3853                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3854      /* [HGM] We seem to have switched variant unexpectedly
3855       * Try to guess new variant from board size
3856       */
3857           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3858           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3859           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3860           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3861           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3862           if(!weird) newVariant = VariantNormal;
3863           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3864           /* Get a move list just to see the header, which
3865              will tell us whether this is really bug or zh */
3866           if (ics_getting_history == H_FALSE) {
3867             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3868             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3869             SendToICS(str);
3870           }
3871     }
3872     
3873     /* Take action if this is the first board of a new game, or of a
3874        different game than is currently being displayed.  */
3875     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3876         relation == RELATION_ISOLATED_BOARD) {
3877         
3878         /* Forget the old game and get the history (if any) of the new one */
3879         if (gameMode != BeginningOfGame) {
3880           Reset(TRUE, TRUE);
3881         }
3882         newGame = TRUE;
3883         if (appData.autoRaiseBoard) BoardToTop();
3884         prevMove = -3;
3885         if (gamenum == -1) {
3886             newGameMode = IcsIdle;
3887         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3888                    appData.getMoveList && !reqFlag) {
3889             /* Need to get game history */
3890             ics_getting_history = H_REQUESTED;
3891             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892             SendToICS(str);
3893         }
3894         
3895         /* Initially flip the board to have black on the bottom if playing
3896            black or if the ICS flip flag is set, but let the user change
3897            it with the Flip View button. */
3898         flipView = appData.autoFlipView ? 
3899           (newGameMode == IcsPlayingBlack) || ics_flip :
3900           appData.flipView;
3901         
3902         /* Done with values from previous mode; copy in new ones */
3903         gameMode = newGameMode;
3904         ModeHighlight();
3905         ics_gamenum = gamenum;
3906         if (gamenum == gs_gamenum) {
3907             int klen = strlen(gs_kind);
3908             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3909             sprintf(str, "ICS %s", gs_kind);
3910             gameInfo.event = StrSave(str);
3911         } else {
3912             gameInfo.event = StrSave("ICS game");
3913         }
3914         gameInfo.site = StrSave(appData.icsHost);
3915         gameInfo.date = PGNDate();
3916         gameInfo.round = StrSave("-");
3917         gameInfo.white = StrSave(white);
3918         gameInfo.black = StrSave(black);
3919         timeControl = basetime * 60 * 1000;
3920         timeControl_2 = 0;
3921         timeIncrement = increment * 1000;
3922         movesPerSession = 0;
3923         gameInfo.timeControl = TimeControlTagValue();
3924         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3927     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3928     setbuf(debugFP, NULL);
3929   }
3930
3931         gameInfo.outOfBook = NULL;
3932         
3933         /* Do we have the ratings? */
3934         if (strcmp(player1Name, white) == 0 &&
3935             strcmp(player2Name, black) == 0) {
3936             if (appData.debugMode)
3937               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3938                       player1Rating, player2Rating);
3939             gameInfo.whiteRating = player1Rating;
3940             gameInfo.blackRating = player2Rating;
3941         } else if (strcmp(player2Name, white) == 0 &&
3942                    strcmp(player1Name, black) == 0) {
3943             if (appData.debugMode)
3944               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3945                       player2Rating, player1Rating);
3946             gameInfo.whiteRating = player2Rating;
3947             gameInfo.blackRating = player1Rating;
3948         }
3949         player1Name[0] = player2Name[0] = NULLCHAR;
3950
3951         /* Silence shouts if requested */
3952         if (appData.quietPlay &&
3953             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3954             SendToICS(ics_prefix);
3955             SendToICS("set shout 0\n");
3956         }
3957     }
3958     
3959     /* Deal with midgame name changes */
3960     if (!newGame) {
3961         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3962             if (gameInfo.white) free(gameInfo.white);
3963             gameInfo.white = StrSave(white);
3964         }
3965         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3966             if (gameInfo.black) free(gameInfo.black);
3967             gameInfo.black = StrSave(black);
3968         }
3969     }
3970     
3971     /* Throw away game result if anything actually changes in examine mode */
3972     if (gameMode == IcsExamining && !newGame) {
3973         gameInfo.result = GameUnfinished;
3974         if (gameInfo.resultDetails != NULL) {
3975             free(gameInfo.resultDetails);
3976             gameInfo.resultDetails = NULL;
3977         }
3978     }
3979     
3980     /* In pausing && IcsExamining mode, we ignore boards coming
3981        in if they are in a different variation than we are. */
3982     if (pauseExamInvalid) return;
3983     if (pausing && gameMode == IcsExamining) {
3984         if (moveNum <= pauseExamForwardMostMove) {
3985             pauseExamInvalid = TRUE;
3986             forwardMostMove = pauseExamForwardMostMove;
3987             return;
3988         }
3989     }
3990     
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3993   }
3994     /* Parse the board */
3995     for (k = 0; k < ranks; k++) {
3996       for (j = 0; j < files; j++)
3997         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3998       if(gameInfo.holdingsWidth > 1) {
3999            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4000            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4001       }
4002     }
4003     CopyBoard(boards[moveNum], board);
4004     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4005     if (moveNum == 0) {
4006         startedFromSetupPosition =
4007           !CompareBoards(board, initialPosition);
4008         if(startedFromSetupPosition)
4009             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4010     }
4011
4012     /* [HGM] Set castling rights. Take the outermost Rooks,
4013        to make it also work for FRC opening positions. Note that board12
4014        is really defective for later FRC positions, as it has no way to
4015        indicate which Rook can castle if they are on the same side of King.
4016        For the initial position we grant rights to the outermost Rooks,
4017        and remember thos rights, and we then copy them on positions
4018        later in an FRC game. This means WB might not recognize castlings with
4019        Rooks that have moved back to their original position as illegal,
4020        but in ICS mode that is not its job anyway.
4021     */
4022     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4023     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4024
4025         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4026             if(board[0][i] == WhiteRook) j = i;
4027         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4028         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4029             if(board[0][i] == WhiteRook) j = i;
4030         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4031         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4032             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4033         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4034         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4035             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4036         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4037
4038         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4039         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4040             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4041         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4042             if(board[BOARD_HEIGHT-1][k] == bKing)
4043                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4044         if(gameInfo.variant == VariantTwoKings) {
4045             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4046             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4047             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4048         }
4049     } else { int r;
4050         r = boards[moveNum][CASTLING][0] = initialRights[0];
4051         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4052         r = boards[moveNum][CASTLING][1] = initialRights[1];
4053         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4054         r = boards[moveNum][CASTLING][3] = initialRights[3];
4055         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4056         r = boards[moveNum][CASTLING][4] = initialRights[4];
4057         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4058         /* wildcastle kludge: always assume King has rights */
4059         r = boards[moveNum][CASTLING][2] = initialRights[2];
4060         r = boards[moveNum][CASTLING][5] = initialRights[5];
4061     }
4062     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4063     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4064
4065     
4066     if (ics_getting_history == H_GOT_REQ_HEADER ||
4067         ics_getting_history == H_GOT_UNREQ_HEADER) {
4068         /* This was an initial position from a move list, not
4069            the current position */
4070         return;
4071     }
4072     
4073     /* Update currentMove and known move number limits */
4074     newMove = newGame || moveNum > forwardMostMove;
4075
4076     if (newGame) {
4077         forwardMostMove = backwardMostMove = currentMove = moveNum;
4078         if (gameMode == IcsExamining && moveNum == 0) {
4079           /* Workaround for ICS limitation: we are not told the wild
4080              type when starting to examine a game.  But if we ask for
4081              the move list, the move list header will tell us */
4082             ics_getting_history = H_REQUESTED;
4083             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4084             SendToICS(str);
4085         }
4086     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4087                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4088 #if ZIPPY
4089         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4090         /* [HGM] applied this also to an engine that is silently watching        */
4091         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4092             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4093             gameInfo.variant == currentlyInitializedVariant) {
4094           takeback = forwardMostMove - moveNum;
4095           for (i = 0; i < takeback; i++) {
4096             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4097             SendToProgram("undo\n", &first);
4098           }
4099         }
4100 #endif
4101
4102         forwardMostMove = moveNum;
4103         if (!pausing || currentMove > forwardMostMove)
4104           currentMove = forwardMostMove;
4105     } else {
4106         /* New part of history that is not contiguous with old part */ 
4107         if (pausing && gameMode == IcsExamining) {
4108             pauseExamInvalid = TRUE;
4109             forwardMostMove = pauseExamForwardMostMove;
4110             return;
4111         }
4112         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4113 #if ZIPPY
4114             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4115                 // [HGM] when we will receive the move list we now request, it will be
4116                 // fed to the engine from the first move on. So if the engine is not
4117                 // in the initial position now, bring it there.
4118                 InitChessProgram(&first, 0);
4119             }
4120 #endif
4121             ics_getting_history = H_REQUESTED;
4122             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4123             SendToICS(str);
4124         }
4125         forwardMostMove = backwardMostMove = currentMove = moveNum;
4126     }
4127     
4128     /* Update the clocks */
4129     if (strchr(elapsed_time, '.')) {
4130       /* Time is in ms */
4131       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4132       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4133     } else {
4134       /* Time is in seconds */
4135       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4136       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4137     }
4138       
4139
4140 #if ZIPPY
4141     if (appData.zippyPlay && newGame &&
4142         gameMode != IcsObserving && gameMode != IcsIdle &&
4143         gameMode != IcsExamining)
4144       ZippyFirstBoard(moveNum, basetime, increment);
4145 #endif
4146     
4147     /* Put the move on the move list, first converting
4148        to canonical algebraic form. */
4149     if (moveNum > 0) {
4150   if (appData.debugMode) {
4151     if (appData.debugMode) { int f = forwardMostMove;
4152         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4153                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4154                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4155     }
4156     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4157     fprintf(debugFP, "moveNum = %d\n", moveNum);
4158     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4159     setbuf(debugFP, NULL);
4160   }
4161         if (moveNum <= backwardMostMove) {
4162             /* We don't know what the board looked like before
4163                this move.  Punt. */
4164             strcpy(parseList[moveNum - 1], move_str);
4165             strcat(parseList[moveNum - 1], " ");
4166             strcat(parseList[moveNum - 1], elapsed_time);
4167             moveList[moveNum - 1][0] = NULLCHAR;
4168         } else if (strcmp(move_str, "none") == 0) {
4169             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4170             /* Again, we don't know what the board looked like;
4171                this is really the start of the game. */
4172             parseList[moveNum - 1][0] = NULLCHAR;
4173             moveList[moveNum - 1][0] = NULLCHAR;
4174             backwardMostMove = moveNum;
4175             startedFromSetupPosition = TRUE;
4176             fromX = fromY = toX = toY = -1;
4177         } else {
4178           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4179           //                 So we parse the long-algebraic move string in stead of the SAN move
4180           int valid; char buf[MSG_SIZ], *prom;
4181
4182           // str looks something like "Q/a1-a2"; kill the slash
4183           if(str[1] == '/') 
4184                 sprintf(buf, "%c%s", str[0], str+2);
4185           else  strcpy(buf, str); // might be castling
4186           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4187                 strcat(buf, prom); // long move lacks promo specification!
4188           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4189                 if(appData.debugMode) 
4190                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4191                 strcpy(move_str, buf);
4192           }
4193           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4194                                 &fromX, &fromY, &toX, &toY, &promoChar)
4195                || ParseOneMove(buf, moveNum - 1, &moveType,
4196                                 &fromX, &fromY, &toX, &toY, &promoChar);
4197           // end of long SAN patch
4198           if (valid) {
4199             (void) CoordsToAlgebraic(boards[moveNum - 1],
4200                                      PosFlags(moveNum - 1),
4201                                      fromY, fromX, toY, toX, promoChar,
4202                                      parseList[moveNum-1]);
4203             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4204               case MT_NONE:
4205               case MT_STALEMATE:
4206               default:
4207                 break;
4208               case MT_CHECK:
4209                 if(gameInfo.variant != VariantShogi)
4210                     strcat(parseList[moveNum - 1], "+");
4211                 break;
4212               case MT_CHECKMATE:
4213               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4214                 strcat(parseList[moveNum - 1], "#");
4215                 break;
4216             }
4217             strcat(parseList[moveNum - 1], " ");
4218             strcat(parseList[moveNum - 1], elapsed_time);
4219             /* currentMoveString is set as a side-effect of ParseOneMove */
4220             strcpy(moveList[moveNum - 1], currentMoveString);
4221             strcat(moveList[moveNum - 1], "\n");
4222           } else {
4223             /* Move from ICS was illegal!?  Punt. */
4224   if (appData.debugMode) {
4225     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4226     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4227   }
4228             strcpy(parseList[moveNum - 1], move_str);
4229             strcat(parseList[moveNum - 1], " ");
4230             strcat(parseList[moveNum - 1], elapsed_time);
4231             moveList[moveNum - 1][0] = NULLCHAR;
4232             fromX = fromY = toX = toY = -1;
4233           }
4234         }
4235   if (appData.debugMode) {
4236     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4237     setbuf(debugFP, NULL);
4238   }
4239
4240 #if ZIPPY
4241         /* Send move to chess program (BEFORE animating it). */
4242         if (appData.zippyPlay && !newGame && newMove && 
4243            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4244
4245             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4246                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4247                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4248                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4249                             move_str);
4250                     DisplayError(str, 0);
4251                 } else {
4252                     if (first.sendTime) {
4253                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4254                     }
4255                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4256                     if (firstMove && !bookHit) {
4257                         firstMove = FALSE;
4258                         if (first.useColors) {
4259                           SendToProgram(gameMode == IcsPlayingWhite ?
4260                                         "white\ngo\n" :
4261                                         "black\ngo\n", &first);
4262                         } else {
4263                           SendToProgram("go\n", &first);
4264                         }
4265                         first.maybeThinking = TRUE;
4266                     }
4267                 }
4268             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4269               if (moveList[moveNum - 1][0] == NULLCHAR) {
4270                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4271                 DisplayError(str, 0);
4272               } else {
4273                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4274                 SendMoveToProgram(moveNum - 1, &first);
4275               }
4276             }
4277         }
4278 #endif
4279     }
4280
4281     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4282         /* If move comes from a remote source, animate it.  If it
4283            isn't remote, it will have already been animated. */
4284         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4285             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4286         }
4287         if (!pausing && appData.highlightLastMove) {
4288             SetHighlights(fromX, fromY, toX, toY);
4289         }
4290     }
4291     
4292     /* Start the clocks */
4293     whiteFlag = blackFlag = FALSE;
4294     appData.clockMode = !(basetime == 0 && increment == 0);
4295     if (ticking == 0) {
4296       ics_clock_paused = TRUE;
4297       StopClocks();
4298     } else if (ticking == 1) {
4299       ics_clock_paused = FALSE;
4300     }
4301     if (gameMode == IcsIdle ||
4302         relation == RELATION_OBSERVING_STATIC ||
4303         relation == RELATION_EXAMINING ||
4304         ics_clock_paused)
4305       DisplayBothClocks();
4306     else
4307       StartClocks();
4308     
4309     /* Display opponents and material strengths */
4310     if (gameInfo.variant != VariantBughouse &&
4311         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4312         if (tinyLayout || smallLayout) {
4313             if(gameInfo.variant == VariantNormal)
4314                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4315                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4316                     basetime, increment);
4317             else
4318                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4319                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4320                     basetime, increment, (int) gameInfo.variant);
4321         } else {
4322             if(gameInfo.variant == VariantNormal)
4323                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4324                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4325                     basetime, increment);
4326             else
4327                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4328                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4329                     basetime, increment, VariantName(gameInfo.variant));
4330         }
4331         DisplayTitle(str);
4332   if (appData.debugMode) {
4333     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4334   }
4335     }
4336
4337
4338     /* Display the board */
4339     if (!pausing && !appData.noGUI) {
4340       
4341       if (appData.premove)
4342           if (!gotPremove || 
4343              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4344              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4345               ClearPremoveHighlights();
4346
4347       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4348       DrawPosition(j, boards[currentMove]);
4349
4350       DisplayMove(moveNum - 1);
4351       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4352             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4353               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4354         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4355       }
4356     }
4357
4358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4359 #if ZIPPY
4360     if(bookHit) { // [HGM] book: simulate book reply
4361         static char bookMove[MSG_SIZ]; // a bit generous?
4362
4363         programStats.nodes = programStats.depth = programStats.time = 
4364         programStats.score = programStats.got_only_move = 0;
4365         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4366
4367         strcpy(bookMove, "move ");
4368         strcat(bookMove, bookHit);
4369         HandleMachineMove(bookMove, &first);
4370     }
4371 #endif
4372 }
4373
4374 void
4375 GetMoveListEvent()
4376 {
4377     char buf[MSG_SIZ];
4378     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4379         ics_getting_history = H_REQUESTED;
4380         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4381         SendToICS(buf);
4382     }
4383 }
4384
4385 void
4386 AnalysisPeriodicEvent(force)
4387      int force;
4388 {
4389     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4390          && !force) || !appData.periodicUpdates)
4391       return;
4392
4393     /* Send . command to Crafty to collect stats */
4394     SendToProgram(".\n", &first);
4395
4396     /* Don't send another until we get a response (this makes
4397        us stop sending to old Crafty's which don't understand
4398        the "." command (sending illegal cmds resets node count & time,
4399        which looks bad)) */
4400     programStats.ok_to_send = 0;
4401 }
4402
4403 void ics_update_width(new_width)
4404         int new_width;
4405 {
4406         ics_printf("set width %d\n", new_width);
4407 }
4408
4409 void
4410 SendMoveToProgram(moveNum, cps)
4411      int moveNum;
4412      ChessProgramState *cps;
4413 {
4414     char buf[MSG_SIZ];
4415
4416     if (cps->useUsermove) {
4417       SendToProgram("usermove ", cps);
4418     }
4419     if (cps->useSAN) {
4420       char *space;
4421       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4422         int len = space - parseList[moveNum];
4423         memcpy(buf, parseList[moveNum], len);
4424         buf[len++] = '\n';
4425         buf[len] = NULLCHAR;
4426       } else {
4427         sprintf(buf, "%s\n", parseList[moveNum]);
4428       }
4429       SendToProgram(buf, cps);
4430     } else {
4431       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4432         AlphaRank(moveList[moveNum], 4);
4433         SendToProgram(moveList[moveNum], cps);
4434         AlphaRank(moveList[moveNum], 4); // and back
4435       } else
4436       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4437        * the engine. It would be nice to have a better way to identify castle 
4438        * moves here. */
4439       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4440                                                                          && cps->useOOCastle) {
4441         int fromX = moveList[moveNum][0] - AAA; 
4442         int fromY = moveList[moveNum][1] - ONE;
4443         int toX = moveList[moveNum][2] - AAA; 
4444         int toY = moveList[moveNum][3] - ONE;
4445         if((boards[moveNum][fromY][fromX] == WhiteKing 
4446             && boards[moveNum][toY][toX] == WhiteRook)
4447            || (boards[moveNum][fromY][fromX] == BlackKing 
4448                && boards[moveNum][toY][toX] == BlackRook)) {
4449           if(toX > fromX) SendToProgram("O-O\n", cps);
4450           else SendToProgram("O-O-O\n", cps);
4451         }
4452         else SendToProgram(moveList[moveNum], cps);
4453       }
4454       else SendToProgram(moveList[moveNum], cps);
4455       /* End of additions by Tord */
4456     }
4457
4458     /* [HGM] setting up the opening has brought engine in force mode! */
4459     /*       Send 'go' if we are in a mode where machine should play. */
4460     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4461         (gameMode == TwoMachinesPlay   ||
4462 #ifdef ZIPPY
4463          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4464 #endif
4465          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4466         SendToProgram("go\n", cps);
4467   if (appData.debugMode) {
4468     fprintf(debugFP, "(extra)\n");
4469   }
4470     }
4471     setboardSpoiledMachineBlack = 0;
4472 }
4473
4474 void
4475 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4476      ChessMove moveType;
4477      int fromX, fromY, toX, toY;
4478 {
4479     char user_move[MSG_SIZ];
4480
4481     switch (moveType) {
4482       default:
4483         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4484                 (int)moveType, fromX, fromY, toX, toY);
4485         DisplayError(user_move + strlen("say "), 0);
4486         break;
4487       case WhiteKingSideCastle:
4488       case BlackKingSideCastle:
4489       case WhiteQueenSideCastleWild:
4490       case BlackQueenSideCastleWild:
4491       /* PUSH Fabien */
4492       case WhiteHSideCastleFR:
4493       case BlackHSideCastleFR:
4494       /* POP Fabien */
4495         sprintf(user_move, "o-o\n");
4496         break;
4497       case WhiteQueenSideCastle:
4498       case BlackQueenSideCastle:
4499       case WhiteKingSideCastleWild:
4500       case BlackKingSideCastleWild:
4501       /* PUSH Fabien */
4502       case WhiteASideCastleFR:
4503       case BlackASideCastleFR:
4504       /* POP Fabien */
4505         sprintf(user_move, "o-o-o\n");
4506         break;
4507       case WhitePromotionQueen:
4508       case BlackPromotionQueen:
4509       case WhitePromotionRook:
4510       case BlackPromotionRook:
4511       case WhitePromotionBishop:
4512       case BlackPromotionBishop:
4513       case WhitePromotionKnight:
4514       case BlackPromotionKnight:
4515       case WhitePromotionKing:
4516       case BlackPromotionKing:
4517       case WhitePromotionChancellor:
4518       case BlackPromotionChancellor:
4519       case WhitePromotionArchbishop:
4520       case BlackPromotionArchbishop:
4521         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4522             sprintf(user_move, "%c%c%c%c=%c\n",
4523                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4524                 PieceToChar(WhiteFerz));
4525         else if(gameInfo.variant == VariantGreat)
4526             sprintf(user_move, "%c%c%c%c=%c\n",
4527                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4528                 PieceToChar(WhiteMan));
4529         else
4530             sprintf(user_move, "%c%c%c%c=%c\n",
4531                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4532                 PieceToChar(PromoPiece(moveType)));
4533         break;
4534       case WhiteDrop:
4535       case BlackDrop:
4536         sprintf(user_move, "%c@%c%c\n",
4537                 ToUpper(PieceToChar((ChessSquare) fromX)),
4538                 AAA + toX, ONE + toY);
4539         break;
4540       case NormalMove:
4541       case WhiteCapturesEnPassant:
4542       case BlackCapturesEnPassant:
4543       case IllegalMove:  /* could be a variant we don't quite understand */
4544         sprintf(user_move, "%c%c%c%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4546         break;
4547     }
4548     SendToICS(user_move);
4549     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4550         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4551 }
4552
4553 void
4554 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4555      int rf, ff, rt, ft;
4556      char promoChar;
4557      char move[7];
4558 {
4559     if (rf == DROP_RANK) {
4560         sprintf(move, "%c@%c%c\n",
4561                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4562     } else {
4563         if (promoChar == 'x' || promoChar == NULLCHAR) {
4564             sprintf(move, "%c%c%c%c\n",
4565                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4566         } else {
4567             sprintf(move, "%c%c%c%c%c\n",
4568                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4569         }
4570     }
4571 }
4572
4573 void
4574 ProcessICSInitScript(f)
4575      FILE *f;
4576 {
4577     char buf[MSG_SIZ];
4578
4579     while (fgets(buf, MSG_SIZ, f)) {
4580         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4581     }
4582
4583     fclose(f);
4584 }
4585
4586
4587 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4588 void
4589 AlphaRank(char *move, int n)
4590 {
4591 //    char *p = move, c; int x, y;
4592
4593     if (appData.debugMode) {
4594         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4595     }
4596
4597     if(move[1]=='*' && 
4598        move[2]>='0' && move[2]<='9' &&
4599        move[3]>='a' && move[3]<='x'    ) {
4600         move[1] = '@';
4601         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4602         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4603     } else
4604     if(move[0]>='0' && move[0]<='9' &&
4605        move[1]>='a' && move[1]<='x' &&
4606        move[2]>='0' && move[2]<='9' &&
4607        move[3]>='a' && move[3]<='x'    ) {
4608         /* input move, Shogi -> normal */
4609         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4610         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4611         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4612         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4613     } else
4614     if(move[1]=='@' &&
4615        move[3]>='0' && move[3]<='9' &&
4616        move[2]>='a' && move[2]<='x'    ) {
4617         move[1] = '*';
4618         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4619         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4620     } else
4621     if(
4622        move[0]>='a' && move[0]<='x' &&
4623        move[3]>='0' && move[3]<='9' &&
4624        move[2]>='a' && move[2]<='x'    ) {
4625          /* output move, normal -> Shogi */
4626         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4627         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4628         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4629         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4630         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4631     }
4632     if (appData.debugMode) {
4633         fprintf(debugFP, "   out = '%s'\n", move);
4634     }
4635 }
4636
4637 /* Parser for moves from gnuchess, ICS, or user typein box */
4638 Boolean
4639 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4640      char *move;
4641      int moveNum;
4642      ChessMove *moveType;
4643      int *fromX, *fromY, *toX, *toY;
4644      char *promoChar;
4645 {       
4646     if (appData.debugMode) {
4647         fprintf(debugFP, "move to parse: %s\n", move);
4648     }
4649     *moveType = yylexstr(moveNum, move);
4650
4651     switch (*moveType) {
4652       case WhitePromotionChancellor:
4653       case BlackPromotionChancellor:
4654       case WhitePromotionArchbishop:
4655       case BlackPromotionArchbishop:
4656       case WhitePromotionQueen:
4657       case BlackPromotionQueen:
4658       case WhitePromotionRook:
4659       case BlackPromotionRook:
4660       case WhitePromotionBishop:
4661       case BlackPromotionBishop:
4662       case WhitePromotionKnight:
4663       case BlackPromotionKnight:
4664       case WhitePromotionKing:
4665       case BlackPromotionKing:
4666       case NormalMove:
4667       case WhiteCapturesEnPassant:
4668       case BlackCapturesEnPassant:
4669       case WhiteKingSideCastle:
4670       case WhiteQueenSideCastle:
4671       case BlackKingSideCastle:
4672       case BlackQueenSideCastle:
4673       case WhiteKingSideCastleWild:
4674       case WhiteQueenSideCastleWild:
4675       case BlackKingSideCastleWild:
4676       case BlackQueenSideCastleWild:
4677       /* Code added by Tord: */
4678       case WhiteHSideCastleFR:
4679       case WhiteASideCastleFR:
4680       case BlackHSideCastleFR:
4681       case BlackASideCastleFR:
4682       /* End of code added by Tord */
4683       case IllegalMove:         /* bug or odd chess variant */
4684         *fromX = currentMoveString[0] - AAA;
4685         *fromY = currentMoveString[1] - ONE;
4686         *toX = currentMoveString[2] - AAA;
4687         *toY = currentMoveString[3] - ONE;
4688         *promoChar = currentMoveString[4];
4689         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4690             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4691     if (appData.debugMode) {
4692         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4693     }
4694             *fromX = *fromY = *toX = *toY = 0;
4695             return FALSE;
4696         }
4697         if (appData.testLegality) {
4698           return (*moveType != IllegalMove);
4699         } else {
4700           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4701                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4702         }
4703
4704       case WhiteDrop:
4705       case BlackDrop:
4706         *fromX = *moveType == WhiteDrop ?
4707           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4708           (int) CharToPiece(ToLower(currentMoveString[0]));
4709         *fromY = DROP_RANK;
4710         *toX = currentMoveString[2] - AAA;
4711         *toY = currentMoveString[3] - ONE;
4712         *promoChar = NULLCHAR;
4713         return TRUE;
4714
4715       case AmbiguousMove:
4716       case ImpossibleMove:
4717       case (ChessMove) 0:       /* end of file */
4718       case ElapsedTime:
4719       case Comment:
4720       case PGNTag:
4721       case NAG:
4722       case WhiteWins:
4723       case BlackWins:
4724       case GameIsDrawn:
4725       default:
4726     if (appData.debugMode) {
4727         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4728     }
4729         /* bug? */
4730         *fromX = *fromY = *toX = *toY = 0;
4731         *promoChar = NULLCHAR;
4732         return FALSE;
4733     }
4734 }
4735
4736
4737 void
4738 ParsePV(char *pv)
4739 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4740   int fromX, fromY, toX, toY; char promoChar;
4741   ChessMove moveType;
4742   Boolean valid;
4743   int nr = 0;
4744
4745   endPV = forwardMostMove;
4746   do {
4747     while(*pv == ' ') pv++;
4748     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4749     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4750 if(appData.debugMode){
4751 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4752 }
4753     if(!valid && nr == 0 &&
4754        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4755         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4756         // Hande case where played move is different from leading PV move
4757         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4758         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4759         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4760         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4761           endPV += 2; // if position different, keep this
4762           moveList[endPV-1][0] = fromX + AAA;
4763           moveList[endPV-1][1] = fromY + ONE;
4764           moveList[endPV-1][2] = toX + AAA;
4765           moveList[endPV-1][3] = toY + ONE;
4766           parseList[endPV-1][0] = NULLCHAR;
4767           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4768         }
4769       }
4770     }
4771     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4772     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4773     nr++;
4774     if(endPV+1 > framePtr) break; // no space, truncate
4775     if(!valid) break;
4776     endPV++;
4777     CopyBoard(boards[endPV], boards[endPV-1]);
4778     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4779     moveList[endPV-1][0] = fromX + AAA;
4780     moveList[endPV-1][1] = fromY + ONE;
4781     moveList[endPV-1][2] = toX + AAA;
4782     moveList[endPV-1][3] = toY + ONE;
4783     parseList[endPV-1][0] = NULLCHAR;
4784   } while(valid);
4785   currentMove = endPV;
4786   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4787   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4788                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4789   DrawPosition(TRUE, boards[currentMove]);
4790 }
4791
4792 static int lastX, lastY;
4793
4794 Boolean
4795 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4796 {
4797         int startPV;
4798
4799         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4800         lastX = x; lastY = y;
4801         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4802         startPV = index;
4803       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4804       index = startPV;
4805         while(buf[index] && buf[index] != '\n') index++;
4806         buf[index] = 0;
4807         ParsePV(buf+startPV);
4808         *start = startPV; *end = index-1;
4809         return TRUE;
4810 }
4811
4812 Boolean
4813 LoadPV(int x, int y)
4814 { // called on right mouse click to load PV
4815   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4816   lastX = x; lastY = y;
4817   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4818   return TRUE;
4819 }
4820
4821 void
4822 UnLoadPV()
4823 {
4824   if(endPV < 0) return;
4825   endPV = -1;
4826   currentMove = forwardMostMove;
4827   ClearPremoveHighlights();
4828   DrawPosition(TRUE, boards[currentMove]);
4829 }
4830
4831 void
4832 MovePV(int x, int y, int h)
4833 { // step through PV based on mouse coordinates (called on mouse move)
4834   int margin = h>>3, step = 0;
4835
4836   if(endPV < 0) return;
4837   // we must somehow check if right button is still down (might be released off board!)
4838   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4839   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4840   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4841   if(!step) return;
4842   lastX = x; lastY = y;
4843   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4844   currentMove += step;
4845   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4846   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4847                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4848   DrawPosition(FALSE, boards[currentMove]);
4849 }
4850
4851
4852 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4853 // All positions will have equal probability, but the current method will not provide a unique
4854 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4855 #define DARK 1
4856 #define LITE 2
4857 #define ANY 3
4858
4859 int squaresLeft[4];
4860 int piecesLeft[(int)BlackPawn];
4861 int seed, nrOfShuffles;
4862
4863 void GetPositionNumber()
4864 {       // sets global variable seed
4865         int i;
4866
4867         seed = appData.defaultFrcPosition;
4868         if(seed < 0) { // randomize based on time for negative FRC position numbers
4869                 for(i=0; i<50; i++) seed += random();
4870                 seed = random() ^ random() >> 8 ^ random() << 8;
4871                 if(seed<0) seed = -seed;
4872         }
4873 }
4874
4875 int put(Board board, int pieceType, int rank, int n, int shade)
4876 // put the piece on the (n-1)-th empty squares of the given shade
4877 {
4878         int i;
4879
4880         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4881                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4882                         board[rank][i] = (ChessSquare) pieceType;
4883                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4884                         squaresLeft[ANY]--;
4885                         piecesLeft[pieceType]--; 
4886                         return i;
4887                 }
4888         }
4889         return -1;
4890 }
4891
4892
4893 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4894 // calculate where the next piece goes, (any empty square), and put it there
4895 {
4896         int i;
4897
4898         i = seed % squaresLeft[shade];
4899         nrOfShuffles *= squaresLeft[shade];
4900         seed /= squaresLeft[shade];
4901         put(board, pieceType, rank, i, shade);
4902 }
4903
4904 void AddTwoPieces(Board board, int pieceType, int rank)
4905 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4906 {
4907         int i, n=squaresLeft[ANY], j=n-1, k;
4908
4909         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4910         i = seed % k;  // pick one
4911         nrOfShuffles *= k;
4912         seed /= k;
4913         while(i >= j) i -= j--;
4914         j = n - 1 - j; i += j;
4915         put(board, pieceType, rank, j, ANY);
4916         put(board, pieceType, rank, i, ANY);
4917 }
4918
4919 void SetUpShuffle(Board board, int number)
4920 {
4921         int i, p, first=1;
4922
4923         GetPositionNumber(); nrOfShuffles = 1;
4924
4925         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4926         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4927         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4928
4929         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4930
4931         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4932             p = (int) board[0][i];
4933             if(p < (int) BlackPawn) piecesLeft[p] ++;
4934             board[0][i] = EmptySquare;
4935         }
4936
4937         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4938             // shuffles restricted to allow normal castling put KRR first
4939             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4940                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4941             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4942                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4943             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4944                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4945             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4946                 put(board, WhiteRook, 0, 0, ANY);
4947             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4948         }
4949
4950         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4951             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4952             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4953                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4954                 while(piecesLeft[p] >= 2) {
4955                     AddOnePiece(board, p, 0, LITE);
4956                     AddOnePiece(board, p, 0, DARK);
4957                 }
4958                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4959             }
4960
4961         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4962             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4963             // but we leave King and Rooks for last, to possibly obey FRC restriction
4964             if(p == (int)WhiteRook) continue;
4965             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4966             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4967         }
4968
4969         // now everything is placed, except perhaps King (Unicorn) and Rooks
4970
4971         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4972             // Last King gets castling rights
4973             while(piecesLeft[(int)WhiteUnicorn]) {
4974                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4975                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4976             }
4977
4978             while(piecesLeft[(int)WhiteKing]) {
4979                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4980                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4981             }
4982
4983
4984         } else {
4985             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4986             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4987         }
4988
4989         // Only Rooks can be left; simply place them all
4990         while(piecesLeft[(int)WhiteRook]) {
4991                 i = put(board, WhiteRook, 0, 0, ANY);
4992                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4993                         if(first) {
4994                                 first=0;
4995                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4996                         }
4997                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4998                 }
4999         }
5000         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5001             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5002         }
5003
5004         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5005 }
5006
5007 int SetCharTable( char *table, const char * map )
5008 /* [HGM] moved here from winboard.c because of its general usefulness */
5009 /*       Basically a safe strcpy that uses the last character as King */
5010 {
5011     int result = FALSE; int NrPieces;
5012
5013     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5014                     && NrPieces >= 12 && !(NrPieces&1)) {
5015         int i; /* [HGM] Accept even length from 12 to 34 */
5016
5017         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5018         for( i=0; i<NrPieces/2-1; i++ ) {
5019             table[i] = map[i];
5020             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5021         }
5022         table[(int) WhiteKing]  = map[NrPieces/2-1];
5023         table[(int) BlackKing]  = map[NrPieces-1];
5024
5025         result = TRUE;
5026     }
5027
5028     return result;
5029 }
5030
5031 void Prelude(Board board)
5032 {       // [HGM] superchess: random selection of exo-pieces
5033         int i, j, k; ChessSquare p; 
5034         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5035
5036         GetPositionNumber(); // use FRC position number
5037
5038         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5039             SetCharTable(pieceToChar, appData.pieceToCharTable);
5040             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5041                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5042         }
5043
5044         j = seed%4;                 seed /= 4; 
5045         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5046         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5047         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5048         j = seed%3 + (seed%3 >= j); seed /= 3; 
5049         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5050         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5051         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5052         j = seed%3;                 seed /= 3; 
5053         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5054         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5055         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5056         j = seed%2 + (seed%2 >= j); seed /= 2; 
5057         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5058         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5059         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5060         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5061         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5062         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5063         put(board, exoPieces[0],    0, 0, ANY);
5064         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5065 }
5066
5067 void
5068 InitPosition(redraw)
5069      int redraw;
5070 {
5071     ChessSquare (* pieces)[BOARD_FILES];
5072     int i, j, pawnRow, overrule,
5073     oldx = gameInfo.boardWidth,
5074     oldy = gameInfo.boardHeight,
5075     oldh = gameInfo.holdingsWidth,
5076     oldv = gameInfo.variant;
5077
5078     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5079
5080     /* [AS] Initialize pv info list [HGM] and game status */
5081     {
5082         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5083             pvInfoList[i].depth = 0;
5084             boards[i][EP_STATUS] = EP_NONE;
5085             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5086         }
5087
5088         initialRulePlies = 0; /* 50-move counter start */
5089
5090         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5091         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5092     }
5093
5094     
5095     /* [HGM] logic here is completely changed. In stead of full positions */
5096     /* the initialized data only consist of the two backranks. The switch */
5097     /* selects which one we will use, which is than copied to the Board   */
5098     /* initialPosition, which for the rest is initialized by Pawns and    */
5099     /* empty squares. This initial position is then copied to boards[0],  */
5100     /* possibly after shuffling, so that it remains available.            */
5101
5102     gameInfo.holdingsWidth = 0; /* default board sizes */
5103     gameInfo.boardWidth    = 8;
5104     gameInfo.boardHeight   = 8;
5105     gameInfo.holdingsSize  = 0;
5106     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5107     for(i=0; i<BOARD_FILES-2; i++)
5108       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5109     initialPosition[EP_STATUS] = EP_NONE;
5110     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5111
5112     switch (gameInfo.variant) {
5113     case VariantFischeRandom:
5114       shuffleOpenings = TRUE;
5115     default:
5116       pieces = FIDEArray;
5117       break;
5118     case VariantShatranj:
5119       pieces = ShatranjArray;
5120       nrCastlingRights = 0;
5121       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5122       break;
5123     case VariantMakruk:
5124       pieces = makrukArray;
5125       nrCastlingRights = 0;
5126       startedFromSetupPosition = TRUE;
5127       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5128       break;
5129     case VariantTwoKings:
5130       pieces = twoKingsArray;
5131       break;
5132     case VariantCapaRandom:
5133       shuffleOpenings = TRUE;
5134     case VariantCapablanca:
5135       pieces = CapablancaArray;
5136       gameInfo.boardWidth = 10;
5137       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5138       break;
5139     case VariantGothic:
5140       pieces = GothicArray;
5141       gameInfo.boardWidth = 10;
5142       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5143       break;
5144     case VariantJanus:
5145       pieces = JanusArray;
5146       gameInfo.boardWidth = 10;
5147       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5148       nrCastlingRights = 6;
5149         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5150         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5151         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5152         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5153         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5154         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5155       break;
5156     case VariantFalcon:
5157       pieces = FalconArray;
5158       gameInfo.boardWidth = 10;
5159       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5160       break;
5161     case VariantXiangqi:
5162       pieces = XiangqiArray;
5163       gameInfo.boardWidth  = 9;
5164       gameInfo.boardHeight = 10;
5165       nrCastlingRights = 0;
5166       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5167       break;
5168     case VariantShogi:
5169       pieces = ShogiArray;
5170       gameInfo.boardWidth  = 9;
5171       gameInfo.boardHeight = 9;
5172       gameInfo.holdingsSize = 7;
5173       nrCastlingRights = 0;
5174       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5175       break;
5176     case VariantCourier:
5177       pieces = CourierArray;
5178       gameInfo.boardWidth  = 12;
5179       nrCastlingRights = 0;
5180       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5181       break;
5182     case VariantKnightmate:
5183       pieces = KnightmateArray;
5184       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5185       break;
5186     case VariantFairy:
5187       pieces = fairyArray;
5188       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5189       break;
5190     case VariantGreat:
5191       pieces = GreatArray;
5192       gameInfo.boardWidth = 10;
5193       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5194       gameInfo.holdingsSize = 8;
5195       break;
5196     case VariantSuper:
5197       pieces = FIDEArray;
5198       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5199       gameInfo.holdingsSize = 8;
5200       startedFromSetupPosition = TRUE;
5201       break;
5202     case VariantCrazyhouse:
5203     case VariantBughouse:
5204       pieces = FIDEArray;
5205       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5206       gameInfo.holdingsSize = 5;
5207       break;
5208     case VariantWildCastle:
5209       pieces = FIDEArray;
5210       /* !!?shuffle with kings guaranteed to be on d or e file */
5211       shuffleOpenings = 1;
5212       break;
5213     case VariantNoCastle:
5214       pieces = FIDEArray;
5215       nrCastlingRights = 0;
5216       /* !!?unconstrained back-rank shuffle */
5217       shuffleOpenings = 1;
5218       break;
5219     }
5220
5221     overrule = 0;
5222     if(appData.NrFiles >= 0) {
5223         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5224         gameInfo.boardWidth = appData.NrFiles;
5225     }
5226     if(appData.NrRanks >= 0) {
5227         gameInfo.boardHeight = appData.NrRanks;
5228     }
5229     if(appData.holdingsSize >= 0) {
5230         i = appData.holdingsSize;
5231         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5232         gameInfo.holdingsSize = i;
5233     }
5234     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5235     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5236         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5237
5238     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5239     if(pawnRow < 1) pawnRow = 1;
5240     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5241
5242     /* User pieceToChar list overrules defaults */
5243     if(appData.pieceToCharTable != NULL)
5244         SetCharTable(pieceToChar, appData.pieceToCharTable);
5245
5246     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5247
5248         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5249             s = (ChessSquare) 0; /* account holding counts in guard band */
5250         for( i=0; i<BOARD_HEIGHT; i++ )
5251             initialPosition[i][j] = s;
5252
5253         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5254         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5255         initialPosition[pawnRow][j] = WhitePawn;
5256         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5257         if(gameInfo.variant == VariantXiangqi) {
5258             if(j&1) {
5259                 initialPosition[pawnRow][j] = 
5260                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5261                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5262                    initialPosition[2][j] = WhiteCannon;
5263                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5264                 }
5265             }
5266         }
5267         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5268     }
5269     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5270
5271             j=BOARD_LEFT+1;
5272             initialPosition[1][j] = WhiteBishop;
5273             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5274             j=BOARD_RGHT-2;
5275             initialPosition[1][j] = WhiteRook;
5276             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5277     }
5278
5279     if( nrCastlingRights == -1) {
5280         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5281         /*       This sets default castling rights from none to normal corners   */
5282         /* Variants with other castling rights must set them themselves above    */
5283         nrCastlingRights = 6;
5284        
5285         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5286         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5287         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5288         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5289         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5290         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5291      }
5292
5293      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5294      if(gameInfo.variant == VariantGreat) { // promotion commoners
5295         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5296         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5297         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5298         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5299      }
5300   if (appData.debugMode) {
5301     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5302   }
5303     if(shuffleOpenings) {
5304         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5305         startedFromSetupPosition = TRUE;
5306     }
5307     if(startedFromPositionFile) {
5308       /* [HGM] loadPos: use PositionFile for every new game */
5309       CopyBoard(initialPosition, filePosition);
5310       for(i=0; i<nrCastlingRights; i++)
5311           initialRights[i] = filePosition[CASTLING][i];
5312       startedFromSetupPosition = TRUE;
5313     }
5314
5315     CopyBoard(boards[0], initialPosition);
5316
5317     if(oldx != gameInfo.boardWidth ||
5318        oldy != gameInfo.boardHeight ||
5319        oldh != gameInfo.holdingsWidth
5320 #ifdef GOTHIC
5321        || oldv == VariantGothic ||        // For licensing popups
5322        gameInfo.variant == VariantGothic
5323 #endif
5324 #ifdef FALCON
5325        || oldv == VariantFalcon ||
5326        gameInfo.variant == VariantFalcon
5327 #endif
5328                                          )
5329             InitDrawingSizes(-2 ,0);
5330
5331     if (redraw)
5332       DrawPosition(TRUE, boards[currentMove]);
5333 }
5334
5335 void
5336 SendBoard(cps, moveNum)
5337      ChessProgramState *cps;
5338      int moveNum;
5339 {
5340     char message[MSG_SIZ];
5341     
5342     if (cps->useSetboard) {
5343       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5344       sprintf(message, "setboard %s\n", fen);
5345       SendToProgram(message, cps);
5346       free(fen);
5347
5348     } else {
5349       ChessSquare *bp;
5350       int i, j;
5351       /* Kludge to set black to move, avoiding the troublesome and now
5352        * deprecated "black" command.
5353        */
5354       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5355
5356       SendToProgram("edit\n", cps);
5357       SendToProgram("#\n", cps);
5358       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5359         bp = &boards[moveNum][i][BOARD_LEFT];
5360         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5361           if ((int) *bp < (int) BlackPawn) {
5362             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5363                     AAA + j, ONE + i);
5364             if(message[0] == '+' || message[0] == '~') {
5365                 sprintf(message, "%c%c%c+\n",
5366                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5367                         AAA + j, ONE + i);
5368             }
5369             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5370                 message[1] = BOARD_RGHT   - 1 - j + '1';
5371                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5372             }
5373             SendToProgram(message, cps);
5374           }
5375         }
5376       }
5377     
5378       SendToProgram("c\n", cps);
5379       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5380         bp = &boards[moveNum][i][BOARD_LEFT];
5381         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5382           if (((int) *bp != (int) EmptySquare)
5383               && ((int) *bp >= (int) BlackPawn)) {
5384             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5385                     AAA + j, ONE + i);
5386             if(message[0] == '+' || message[0] == '~') {
5387                 sprintf(message, "%c%c%c+\n",
5388                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5389                         AAA + j, ONE + i);
5390             }
5391             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5392                 message[1] = BOARD_RGHT   - 1 - j + '1';
5393                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5394             }
5395             SendToProgram(message, cps);
5396           }
5397         }
5398       }
5399     
5400       SendToProgram(".\n", cps);
5401     }
5402     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5403 }
5404
5405 static int autoQueen; // [HGM] oneclick
5406
5407 int
5408 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5409 {
5410     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5411     /* [HGM] add Shogi promotions */
5412     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5413     ChessSquare piece;
5414     ChessMove moveType;
5415     Boolean premove;
5416
5417     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5418     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5419
5420     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5421       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5422         return FALSE;
5423
5424     piece = boards[currentMove][fromY][fromX];
5425     if(gameInfo.variant == VariantShogi) {
5426         promotionZoneSize = 3;
5427         highestPromotingPiece = (int)WhiteFerz;
5428     } else if(gameInfo.variant == VariantMakruk) {
5429         promotionZoneSize = 3;
5430     }
5431
5432     // next weed out all moves that do not touch the promotion zone at all
5433     if((int)piece >= BlackPawn) {
5434         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5435              return FALSE;
5436         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5437     } else {
5438         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5439            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5440     }
5441
5442     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5443
5444     // weed out mandatory Shogi promotions
5445     if(gameInfo.variant == VariantShogi) {
5446         if(piece >= BlackPawn) {
5447             if(toY == 0 && piece == BlackPawn ||
5448                toY == 0 && piece == BlackQueen ||
5449                toY <= 1 && piece == BlackKnight) {
5450                 *promoChoice = '+';
5451                 return FALSE;
5452             }
5453         } else {
5454             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5455                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5456                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5457                 *promoChoice = '+';
5458                 return FALSE;
5459             }
5460         }
5461     }
5462
5463     // weed out obviously illegal Pawn moves
5464     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5465         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5466         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5467         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5468         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5469         // note we are not allowed to test for valid (non-)capture, due to premove
5470     }
5471
5472     // we either have a choice what to promote to, or (in Shogi) whether to promote
5473     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5474         *promoChoice = PieceToChar(BlackFerz);  // no choice
5475         return FALSE;
5476     }
5477     if(autoQueen) { // predetermined
5478         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5479              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5480         else *promoChoice = PieceToChar(BlackQueen);
5481         return FALSE;
5482     }
5483
5484     // suppress promotion popup on illegal moves that are not premoves
5485     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5486               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5487     if(appData.testLegality && !premove) {
5488         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5489                         fromY, fromX, toY, toX, NULLCHAR);
5490         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5491            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5492             return FALSE;
5493     }
5494
5495     return TRUE;
5496 }
5497
5498 int
5499 InPalace(row, column)
5500      int row, column;
5501 {   /* [HGM] for Xiangqi */
5502     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5503          column < (BOARD_WIDTH + 4)/2 &&
5504          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5505     return FALSE;
5506 }
5507
5508 int
5509 PieceForSquare (x, y)
5510      int x;
5511      int y;
5512 {
5513   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5514      return -1;
5515   else
5516      return boards[currentMove][y][x];
5517 }
5518
5519 int
5520 OKToStartUserMove(x, y)
5521      int x, y;
5522 {
5523     ChessSquare from_piece;
5524     int white_piece;
5525
5526     if (matchMode) return FALSE;
5527     if (gameMode == EditPosition) return TRUE;
5528
5529     if (x >= 0 && y >= 0)
5530       from_piece = boards[currentMove][y][x];
5531     else
5532       from_piece = EmptySquare;
5533
5534     if (from_piece == EmptySquare) return FALSE;
5535
5536     white_piece = (int)from_piece >= (int)WhitePawn &&
5537       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5538
5539     switch (gameMode) {
5540       case PlayFromGameFile:
5541       case AnalyzeFile:
5542       case TwoMachinesPlay:
5543       case EndOfGame:
5544         return FALSE;
5545
5546       case IcsObserving:
5547       case IcsIdle:
5548         return FALSE;
5549
5550       case MachinePlaysWhite:
5551       case IcsPlayingBlack:
5552         if (appData.zippyPlay) return FALSE;
5553         if (white_piece) {
5554             DisplayMoveError(_("You are playing Black"));
5555             return FALSE;
5556         }
5557         break;
5558
5559       case MachinePlaysBlack:
5560       case IcsPlayingWhite:
5561         if (appData.zippyPlay) return FALSE;
5562         if (!white_piece) {
5563             DisplayMoveError(_("You are playing White"));
5564             return FALSE;
5565         }
5566         break;
5567
5568       case EditGame:
5569         if (!white_piece && WhiteOnMove(currentMove)) {
5570             DisplayMoveError(_("It is White's turn"));
5571             return FALSE;
5572         }           
5573         if (white_piece && !WhiteOnMove(currentMove)) {
5574             DisplayMoveError(_("It is Black's turn"));
5575             return FALSE;
5576         }           
5577         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5578             /* Editing correspondence game history */
5579             /* Could disallow this or prompt for confirmation */
5580             cmailOldMove = -1;
5581         }
5582         break;
5583
5584       case BeginningOfGame:
5585         if (appData.icsActive) return FALSE;
5586         if (!appData.noChessProgram) {
5587             if (!white_piece) {
5588                 DisplayMoveError(_("You are playing White"));
5589                 return FALSE;
5590             }
5591         }
5592         break;
5593         
5594       case Training:
5595         if (!white_piece && WhiteOnMove(currentMove)) {
5596             DisplayMoveError(_("It is White's turn"));
5597             return FALSE;
5598         }           
5599         if (white_piece && !WhiteOnMove(currentMove)) {
5600             DisplayMoveError(_("It is Black's turn"));
5601             return FALSE;
5602         }           
5603         break;
5604
5605       default:
5606       case IcsExamining:
5607         break;
5608     }
5609     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5610         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5611         && gameMode != AnalyzeFile && gameMode != Training) {
5612         DisplayMoveError(_("Displayed position is not current"));
5613         return FALSE;
5614     }
5615     return TRUE;
5616 }
5617
5618 Boolean
5619 OnlyMove(int *x, int *y, Boolean captures) {
5620     DisambiguateClosure cl;
5621     if (appData.zippyPlay) return FALSE;
5622     switch(gameMode) {
5623       case MachinePlaysBlack:
5624       case IcsPlayingWhite:
5625       case BeginningOfGame:
5626         if(!WhiteOnMove(currentMove)) return FALSE;
5627         break;
5628       case MachinePlaysWhite:
5629       case IcsPlayingBlack:
5630         if(WhiteOnMove(currentMove)) return FALSE;
5631         break;
5632       default:
5633         return FALSE;
5634     }
5635     cl.pieceIn = EmptySquare; 
5636     cl.rfIn = *y;
5637     cl.ffIn = *x;
5638     cl.rtIn = -1;
5639     cl.ftIn = -1;
5640     cl.promoCharIn = NULLCHAR;
5641     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5642     if( cl.kind == NormalMove ||
5643         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5644         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5645         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5646         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5647       fromX = cl.ff;
5648       fromY = cl.rf;
5649       *x = cl.ft;
5650       *y = cl.rt;
5651       return TRUE;
5652     }
5653     if(cl.kind != ImpossibleMove) return FALSE;
5654     cl.pieceIn = EmptySquare;
5655     cl.rfIn = -1;
5656     cl.ffIn = -1;
5657     cl.rtIn = *y;
5658     cl.ftIn = *x;
5659     cl.promoCharIn = NULLCHAR;
5660     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5661     if( cl.kind == NormalMove ||
5662         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5663         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5664         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5665         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5666       fromX = cl.ff;
5667       fromY = cl.rf;
5668       *x = cl.ft;
5669       *y = cl.rt;
5670       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5671       return TRUE;
5672     }
5673     return FALSE;
5674 }
5675
5676 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5677 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5678 int lastLoadGameUseList = FALSE;
5679 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5680 ChessMove lastLoadGameStart = (ChessMove) 0;
5681
5682 ChessMove
5683 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5684      int fromX, fromY, toX, toY;
5685      int promoChar;
5686      Boolean captureOwn;
5687 {
5688     ChessMove moveType;
5689     ChessSquare pdown, pup;
5690
5691     /* Check if the user is playing in turn.  This is complicated because we
5692        let the user "pick up" a piece before it is his turn.  So the piece he
5693        tried to pick up may have been captured by the time he puts it down!
5694        Therefore we use the color the user is supposed to be playing in this
5695        test, not the color of the piece that is currently on the starting
5696        square---except in EditGame mode, where the user is playing both
5697        sides; fortunately there the capture race can't happen.  (It can
5698        now happen in IcsExamining mode, but that's just too bad.  The user
5699        will get a somewhat confusing message in that case.)
5700        */
5701
5702     switch (gameMode) {
5703       case PlayFromGameFile:
5704       case AnalyzeFile:
5705       case TwoMachinesPlay:
5706       case EndOfGame:
5707       case IcsObserving:
5708       case IcsIdle:
5709         /* We switched into a game mode where moves are not accepted,
5710            perhaps while the mouse button was down. */
5711         return ImpossibleMove;
5712
5713       case MachinePlaysWhite:
5714         /* User is moving for Black */
5715         if (WhiteOnMove(currentMove)) {
5716             DisplayMoveError(_("It is White's turn"));
5717             return ImpossibleMove;
5718         }
5719         break;
5720
5721       case MachinePlaysBlack:
5722         /* User is moving for White */
5723         if (!WhiteOnMove(currentMove)) {
5724             DisplayMoveError(_("It is Black's turn"));
5725             return ImpossibleMove;
5726         }
5727         break;
5728
5729       case EditGame:
5730       case IcsExamining:
5731       case BeginningOfGame:
5732       case AnalyzeMode:
5733       case Training:
5734         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5735             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5736             /* User is moving for Black */
5737             if (WhiteOnMove(currentMove)) {
5738                 DisplayMoveError(_("It is White's turn"));
5739                 return ImpossibleMove;
5740             }
5741         } else {
5742             /* User is moving for White */
5743             if (!WhiteOnMove(currentMove)) {
5744                 DisplayMoveError(_("It is Black's turn"));
5745                 return ImpossibleMove;
5746             }
5747         }
5748         break;
5749
5750       case IcsPlayingBlack:
5751         /* User is moving for Black */
5752         if (WhiteOnMove(currentMove)) {
5753             if (!appData.premove) {
5754                 DisplayMoveError(_("It is White's turn"));
5755             } else if (toX >= 0 && toY >= 0) {
5756                 premoveToX = toX;
5757                 premoveToY = toY;
5758                 premoveFromX = fromX;
5759                 premoveFromY = fromY;
5760                 premovePromoChar = promoChar;
5761                 gotPremove = 1;
5762                 if (appData.debugMode) 
5763                     fprintf(debugFP, "Got premove: fromX %d,"
5764                             "fromY %d, toX %d, toY %d\n",
5765                             fromX, fromY, toX, toY);
5766             }
5767             return ImpossibleMove;
5768         }
5769         break;
5770
5771       case IcsPlayingWhite:
5772         /* User is moving for White */
5773         if (!WhiteOnMove(currentMove)) {
5774             if (!appData.premove) {
5775                 DisplayMoveError(_("It is Black's turn"));
5776             } else if (toX >= 0 && toY >= 0) {
5777                 premoveToX = toX;
5778                 premoveToY = toY;
5779                 premoveFromX = fromX;
5780                 premoveFromY = fromY;
5781                 premovePromoChar = promoChar;
5782                 gotPremove = 1;
5783                 if (appData.debugMode) 
5784                     fprintf(debugFP, "Got premove: fromX %d,"
5785                             "fromY %d, toX %d, toY %d\n",
5786                             fromX, fromY, toX, toY);
5787             }
5788             return ImpossibleMove;
5789         }
5790         break;
5791
5792       default:
5793         break;
5794
5795       case EditPosition:
5796         /* EditPosition, empty square, or different color piece;
5797            click-click move is possible */
5798         if (toX == -2 || toY == -2) {
5799             boards[0][fromY][fromX] = EmptySquare;
5800             return AmbiguousMove;
5801         } else if (toX >= 0 && toY >= 0) {
5802             boards[0][toY][toX] = boards[0][fromY][fromX];
5803             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5804                 if(boards[0][fromY][0] != EmptySquare) {
5805                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5806                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5807                 }
5808             } else
5809             if(fromX == BOARD_RGHT+1) {
5810                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5811                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5812                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5813                 }
5814             } else
5815             boards[0][fromY][fromX] = EmptySquare;
5816             return AmbiguousMove;
5817         }
5818         return ImpossibleMove;
5819     }
5820
5821     if(toX < 0 || toY < 0) return ImpossibleMove;
5822     pdown = boards[currentMove][fromY][fromX];
5823     pup = boards[currentMove][toY][toX];
5824
5825     /* [HGM] If move started in holdings, it means a drop */
5826     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5827          if( pup != EmptySquare ) return ImpossibleMove;
5828          if(appData.testLegality) {
5829              /* it would be more logical if LegalityTest() also figured out
5830               * which drops are legal. For now we forbid pawns on back rank.
5831               * Shogi is on its own here...
5832               */
5833              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5834                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5835                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5836          }
5837          return WhiteDrop; /* Not needed to specify white or black yet */
5838     }
5839
5840     /* [HGM] always test for legality, to get promotion info */
5841     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5842                                          fromY, fromX, toY, toX, promoChar);
5843     /* [HGM] but possibly ignore an IllegalMove result */
5844     if (appData.testLegality) {
5845         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5846             DisplayMoveError(_("Illegal move"));
5847             return ImpossibleMove;
5848         }
5849     }
5850
5851     return moveType;
5852     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5853        function is made into one that returns an OK move type if FinishMove
5854        should be called. This to give the calling driver routine the
5855        opportunity to finish the userMove input with a promotion popup,
5856        without bothering the user with this for invalid or illegal moves */
5857
5858 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5859 }
5860
5861 /* Common tail of UserMoveEvent and DropMenuEvent */
5862 int
5863 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5864      ChessMove moveType;
5865      int fromX, fromY, toX, toY;
5866      /*char*/int promoChar;
5867 {
5868     char *bookHit = 0;
5869
5870     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5871         // [HGM] superchess: suppress promotions to non-available piece
5872         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5873         if(WhiteOnMove(currentMove)) {
5874             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5875         } else {
5876             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5877         }
5878     }
5879
5880     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5881        move type in caller when we know the move is a legal promotion */
5882     if(moveType == NormalMove && promoChar)
5883         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5884
5885     /* [HGM] convert drag-and-drop piece drops to standard form */
5886     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5887          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5888            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5889                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5890            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5891            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5892            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5893            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5894          fromY = DROP_RANK;
5895     }
5896
5897     /* [HGM] <popupFix> The following if has been moved here from
5898        UserMoveEvent(). Because it seemed to belong here (why not allow
5899        piece drops in training games?), and because it can only be
5900        performed after it is known to what we promote. */
5901     if (gameMode == Training) {
5902       /* compare the move played on the board to the next move in the
5903        * game. If they match, display the move and the opponent's response. 
5904        * If they don't match, display an error message.
5905        */
5906       int saveAnimate;
5907       Board testBoard;
5908       CopyBoard(testBoard, boards[currentMove]);
5909       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5910
5911       if (CompareBoards(testBoard, boards[currentMove+1])) {
5912         ForwardInner(currentMove+1);
5913
5914         /* Autoplay the opponent's response.
5915          * if appData.animate was TRUE when Training mode was entered,
5916          * the response will be animated.
5917          */
5918         saveAnimate = appData.animate;
5919         appData.animate = animateTraining;
5920         ForwardInner(currentMove+1);
5921         appData.animate = saveAnimate;
5922
5923         /* check for the end of the game */
5924         if (currentMove >= forwardMostMove) {
5925           gameMode = PlayFromGameFile;
5926           ModeHighlight();
5927           SetTrainingModeOff();
5928           DisplayInformation(_("End of game"));
5929         }
5930       } else {
5931         DisplayError(_("Incorrect move"), 0);
5932       }
5933       return 1;
5934     }
5935
5936   /* Ok, now we know that the move is good, so we can kill
5937      the previous line in Analysis Mode */
5938   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5939                                 && currentMove < forwardMostMove) {
5940     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5941   }
5942
5943   /* If we need the chess program but it's dead, restart it */
5944   ResurrectChessProgram();
5945
5946   /* A user move restarts a paused game*/
5947   if (pausing)
5948     PauseEvent();
5949
5950   thinkOutput[0] = NULLCHAR;
5951
5952   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5953
5954   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5955
5956   if (gameMode == BeginningOfGame) {
5957     if (appData.noChessProgram) {
5958       gameMode = EditGame;
5959       SetGameInfo();
5960     } else {
5961       char buf[MSG_SIZ];
5962       gameMode = MachinePlaysBlack;
5963       StartClocks();
5964       SetGameInfo();
5965       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5966       DisplayTitle(buf);
5967       if (first.sendName) {
5968         sprintf(buf, "name %s\n", gameInfo.white);
5969         SendToProgram(buf, &first);
5970       }
5971       StartClocks();
5972     }
5973     ModeHighlight();
5974   }
5975
5976   /* Relay move to ICS or chess engine */
5977   if (appData.icsActive) {
5978     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5979         gameMode == IcsExamining) {
5980       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5981         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5982         SendToICS("draw ");
5983         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5984       }
5985       // also send plain move, in case ICS does not understand atomic claims
5986       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5987       ics_user_moved = 1;
5988     }
5989   } else {
5990     if (first.sendTime && (gameMode == BeginningOfGame ||
5991                            gameMode == MachinePlaysWhite ||
5992                            gameMode == MachinePlaysBlack)) {
5993       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5994     }
5995     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5996          // [HGM] book: if program might be playing, let it use book
5997         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5998         first.maybeThinking = TRUE;
5999     } else SendMoveToProgram(forwardMostMove-1, &first);
6000     if (currentMove == cmailOldMove + 1) {
6001       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6002     }
6003   }
6004
6005   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6006
6007   switch (gameMode) {
6008   case EditGame:
6009     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6010     case MT_NONE:
6011     case MT_CHECK:
6012       break;
6013     case MT_CHECKMATE:
6014     case MT_STAINMATE:
6015       if (WhiteOnMove(currentMove)) {
6016         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6017       } else {
6018         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6019       }
6020       break;
6021     case MT_STALEMATE:
6022       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6023       break;
6024     }
6025     break;
6026     
6027   case MachinePlaysBlack:
6028   case MachinePlaysWhite:
6029     /* disable certain menu options while machine is thinking */
6030     SetMachineThinkingEnables();
6031     break;
6032
6033   default:
6034     break;
6035   }
6036
6037   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6038         
6039   if(bookHit) { // [HGM] book: simulate book reply
6040         static char bookMove[MSG_SIZ]; // a bit generous?
6041
6042         programStats.nodes = programStats.depth = programStats.time = 
6043         programStats.score = programStats.got_only_move = 0;
6044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6045
6046         strcpy(bookMove, "move ");
6047         strcat(bookMove, bookHit);
6048         HandleMachineMove(bookMove, &first);
6049   }
6050   return 1;
6051 }
6052
6053 void
6054 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6055      int fromX, fromY, toX, toY;
6056      int promoChar;
6057 {
6058     /* [HGM] This routine was added to allow calling of its two logical
6059        parts from other modules in the old way. Before, UserMoveEvent()
6060        automatically called FinishMove() if the move was OK, and returned
6061        otherwise. I separated the two, in order to make it possible to
6062        slip a promotion popup in between. But that it always needs two
6063        calls, to the first part, (now called UserMoveTest() ), and to
6064        FinishMove if the first part succeeded. Calls that do not need
6065        to do anything in between, can call this routine the old way. 
6066     */
6067     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6068 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6069     if(moveType == AmbiguousMove)
6070         DrawPosition(FALSE, boards[currentMove]);
6071     else if(moveType != ImpossibleMove && moveType != Comment)
6072         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6073 }
6074
6075 void
6076 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6077      Board board;
6078      int flags;
6079      ChessMove kind;
6080      int rf, ff, rt, ft;
6081      VOIDSTAR closure;
6082 {
6083     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6084     Markers *m = (Markers *) closure;
6085     if(rf == fromY && ff == fromX)
6086         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6087                          || kind == WhiteCapturesEnPassant
6088                          || kind == BlackCapturesEnPassant);
6089     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6090 }
6091
6092 void
6093 MarkTargetSquares(int clear)
6094 {
6095   int x, y;
6096   if(!appData.markers || !appData.highlightDragging || 
6097      !appData.testLegality || gameMode == EditPosition) return;
6098   if(clear) {
6099     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6100   } else {
6101     int capt = 0;
6102     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6103     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6104       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6105       if(capt)
6106       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6107     }
6108   }
6109   DrawPosition(TRUE, NULL);
6110 }
6111
6112 void LeftClick(ClickType clickType, int xPix, int yPix)
6113 {
6114     int x, y;
6115     Boolean saveAnimate;
6116     static int second = 0, promotionChoice = 0;
6117     char promoChoice = NULLCHAR;
6118
6119     if(appData.seekGraph && appData.icsActive && loggedOn &&
6120         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6121         SeekGraphClick(clickType, xPix, yPix, 0);
6122         return;
6123     }
6124
6125     if (clickType == Press) ErrorPopDown();
6126     MarkTargetSquares(1);
6127
6128     x = EventToSquare(xPix, BOARD_WIDTH);
6129     y = EventToSquare(yPix, BOARD_HEIGHT);
6130     if (!flipView && y >= 0) {
6131         y = BOARD_HEIGHT - 1 - y;
6132     }
6133     if (flipView && x >= 0) {
6134         x = BOARD_WIDTH - 1 - x;
6135     }
6136
6137     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6138         if(clickType == Release) return; // ignore upclick of click-click destination
6139         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6140         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6141         if(gameInfo.holdingsWidth && 
6142                 (WhiteOnMove(currentMove) 
6143                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6144                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6145             // click in right holdings, for determining promotion piece
6146             ChessSquare p = boards[currentMove][y][x];
6147             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6148             if(p != EmptySquare) {
6149                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6150                 fromX = fromY = -1;
6151                 return;
6152             }
6153         }
6154         DrawPosition(FALSE, boards[currentMove]);
6155         return;
6156     }
6157
6158     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6159     if(clickType == Press
6160             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6161               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6162               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6163         return;
6164
6165     autoQueen = appData.alwaysPromoteToQueen;
6166
6167     if (fromX == -1) {
6168       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6169         if (clickType == Press) {
6170             /* First square */
6171             if (OKToStartUserMove(x, y)) {
6172                 fromX = x;
6173                 fromY = y;
6174                 second = 0;
6175                 MarkTargetSquares(0);
6176                 DragPieceBegin(xPix, yPix);
6177                 if (appData.highlightDragging) {
6178                     SetHighlights(x, y, -1, -1);
6179                 }
6180             }
6181         }
6182         return;
6183       }
6184     }
6185
6186     /* fromX != -1 */
6187     if (clickType == Press && gameMode != EditPosition) {
6188         ChessSquare fromP;
6189         ChessSquare toP;
6190         int frc;
6191
6192         // ignore off-board to clicks
6193         if(y < 0 || x < 0) return;
6194
6195         /* Check if clicking again on the same color piece */
6196         fromP = boards[currentMove][fromY][fromX];
6197         toP = boards[currentMove][y][x];
6198         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6199         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6200              WhitePawn <= toP && toP <= WhiteKing &&
6201              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6202              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6203             (BlackPawn <= fromP && fromP <= BlackKing && 
6204              BlackPawn <= toP && toP <= BlackKing &&
6205              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6206              !(fromP == BlackKing && toP == BlackRook && frc))) {
6207             /* Clicked again on same color piece -- changed his mind */
6208             second = (x == fromX && y == fromY);
6209            if(!second || !OnlyMove(&x, &y, TRUE)) {
6210             if (appData.highlightDragging) {
6211                 SetHighlights(x, y, -1, -1);
6212             } else {
6213                 ClearHighlights();
6214             }
6215             if (OKToStartUserMove(x, y)) {
6216                 fromX = x;
6217                 fromY = y;
6218                 MarkTargetSquares(0);
6219                 DragPieceBegin(xPix, yPix);
6220             }
6221             return;
6222            }
6223         }
6224         // ignore clicks on holdings
6225         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6226     }
6227
6228     if (clickType == Release && x == fromX && y == fromY) {
6229         DragPieceEnd(xPix, yPix);
6230         if (appData.animateDragging) {
6231             /* Undo animation damage if any */
6232             DrawPosition(FALSE, NULL);
6233         }
6234         if (second) {
6235             /* Second up/down in same square; just abort move */
6236             second = 0;
6237             fromX = fromY = -1;
6238             ClearHighlights();
6239             gotPremove = 0;
6240             ClearPremoveHighlights();
6241         } else {
6242             /* First upclick in same square; start click-click mode */
6243             SetHighlights(x, y, -1, -1);
6244         }
6245         return;
6246     }
6247
6248     /* we now have a different from- and (possibly off-board) to-square */
6249     /* Completed move */
6250     toX = x;
6251     toY = y;
6252     saveAnimate = appData.animate;
6253     if (clickType == Press) {
6254         /* Finish clickclick move */
6255         if (appData.animate || appData.highlightLastMove) {
6256             SetHighlights(fromX, fromY, toX, toY);
6257         } else {
6258             ClearHighlights();
6259         }
6260     } else {
6261         /* Finish drag move */
6262         if (appData.highlightLastMove) {
6263             SetHighlights(fromX, fromY, toX, toY);
6264         } else {
6265             ClearHighlights();
6266         }
6267         DragPieceEnd(xPix, yPix);
6268         /* Don't animate move and drag both */
6269         appData.animate = FALSE;
6270     }
6271
6272     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6273     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6274         ChessSquare piece = boards[currentMove][fromY][fromX];
6275         if(gameMode == EditPosition && piece != EmptySquare &&
6276            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6277             int n;
6278              
6279             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6280                 n = PieceToNumber(piece - (int)BlackPawn);
6281                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6282                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6283                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6284             } else
6285             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6286                 n = PieceToNumber(piece);
6287                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6288                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6289                 boards[currentMove][n][BOARD_WIDTH-2]++;
6290             }
6291             boards[currentMove][fromY][fromX] = EmptySquare;
6292         }
6293         ClearHighlights();
6294         fromX = fromY = -1;
6295         DrawPosition(TRUE, boards[currentMove]);
6296         return;
6297     }
6298
6299     // off-board moves should not be highlighted
6300     if(x < 0 || x < 0) ClearHighlights();
6301
6302     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6303         SetHighlights(fromX, fromY, toX, toY);
6304         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6305             // [HGM] super: promotion to captured piece selected from holdings
6306             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6307             promotionChoice = TRUE;
6308             // kludge follows to temporarily execute move on display, without promoting yet
6309             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6310             boards[currentMove][toY][toX] = p;
6311             DrawPosition(FALSE, boards[currentMove]);
6312             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6313             boards[currentMove][toY][toX] = q;
6314             DisplayMessage("Click in holdings to choose piece", "");
6315             return;
6316         }
6317         PromotionPopUp();
6318     } else {
6319         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6320         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6321         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6322         fromX = fromY = -1;
6323     }
6324     appData.animate = saveAnimate;
6325     if (appData.animate || appData.animateDragging) {
6326         /* Undo animation damage if needed */
6327         DrawPosition(FALSE, NULL);
6328     }
6329 }
6330
6331 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6332 {   // front-end-free part taken out of PieceMenuPopup
6333     int whichMenu; int xSqr, ySqr;
6334
6335     if(seekGraphUp) { // [HGM] seekgraph
6336         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6337         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6338         return -2;
6339     }
6340
6341     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6342          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6343         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6344         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6345         return -2;
6346     }
6347
6348     xSqr = EventToSquare(x, BOARD_WIDTH);
6349     ySqr = EventToSquare(y, BOARD_HEIGHT);
6350     if (action == Release) UnLoadPV(); // [HGM] pv
6351     if (action != Press) return -2; // return code to be ignored
6352     switch (gameMode) {
6353       case IcsExamining:
6354         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6355       case EditPosition:
6356         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6357         if (xSqr < 0 || ySqr < 0) return -1;\r
6358         whichMenu = 0; // edit-position menu
6359         break;
6360       case IcsObserving:
6361         if(!appData.icsEngineAnalyze) return -1;
6362       case IcsPlayingWhite:
6363       case IcsPlayingBlack:
6364         if(!appData.zippyPlay) goto noZip;
6365       case AnalyzeMode:
6366       case AnalyzeFile:
6367       case MachinePlaysWhite:
6368       case MachinePlaysBlack:
6369       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6370         if (!appData.dropMenu) {
6371           LoadPV(x, y);
6372           return 2; // flag front-end to grab mouse events
6373         }
6374         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6375            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6376       case EditGame:
6377       noZip:
6378         if (xSqr < 0 || ySqr < 0) return -1;
6379         if (!appData.dropMenu || appData.testLegality &&
6380             gameInfo.variant != VariantBughouse &&
6381             gameInfo.variant != VariantCrazyhouse) return -1;
6382         whichMenu = 1; // drop menu
6383         break;
6384       default:
6385         return -1;
6386     }
6387
6388     if (((*fromX = xSqr) < 0) ||
6389         ((*fromY = ySqr) < 0)) {
6390         *fromX = *fromY = -1;
6391         return -1;
6392     }
6393     if (flipView)
6394       *fromX = BOARD_WIDTH - 1 - *fromX;
6395     else
6396       *fromY = BOARD_HEIGHT - 1 - *fromY;
6397
6398     return whichMenu;
6399 }
6400
6401 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6402 {
6403 //    char * hint = lastHint;
6404     FrontEndProgramStats stats;
6405
6406     stats.which = cps == &first ? 0 : 1;
6407     stats.depth = cpstats->depth;
6408     stats.nodes = cpstats->nodes;
6409     stats.score = cpstats->score;
6410     stats.time = cpstats->time;
6411     stats.pv = cpstats->movelist;
6412     stats.hint = lastHint;
6413     stats.an_move_index = 0;
6414     stats.an_move_count = 0;
6415
6416     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6417         stats.hint = cpstats->move_name;
6418         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6419         stats.an_move_count = cpstats->nr_moves;
6420     }
6421
6422     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6423
6424     SetProgramStats( &stats );
6425 }
6426
6427 int
6428 Adjudicate(ChessProgramState *cps)
6429 {       // [HGM] some adjudications useful with buggy engines
6430         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6431         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6432         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6433         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6434         int k, count = 0; static int bare = 1;
6435         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6436         Boolean canAdjudicate = !appData.icsActive;
6437
6438         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6439         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6440             if( appData.testLegality )
6441             {   /* [HGM] Some more adjudications for obstinate engines */
6442                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6443                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6444                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6445                 static int moveCount = 6;
6446                 ChessMove result;
6447                 char *reason = NULL;
6448
6449                 /* Count what is on board. */
6450                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6451                 {   ChessSquare p = boards[forwardMostMove][i][j];
6452                     int m=i;
6453
6454                     switch((int) p)
6455                     {   /* count B,N,R and other of each side */
6456                         case WhiteKing:
6457                         case BlackKing:
6458                              NrK++; break; // [HGM] atomic: count Kings
6459                         case WhiteKnight:
6460                              NrWN++; break;
6461                         case WhiteBishop:
6462                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6463                              bishopsColor |= 1 << ((i^j)&1);
6464                              NrWB++; break;
6465                         case BlackKnight:
6466                              NrBN++; break;
6467                         case BlackBishop:
6468                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6469                              bishopsColor |= 1 << ((i^j)&1);
6470                              NrBB++; break;
6471                         case WhiteRook:
6472                              NrWR++; break;
6473                         case BlackRook:
6474                              NrBR++; break;
6475                         case WhiteQueen:
6476                              NrWQ++; break;
6477                         case BlackQueen:
6478                              NrBQ++; break;
6479                         case EmptySquare: 
6480                              break;
6481                         case BlackPawn:
6482                              m = 7-i;
6483                         case WhitePawn:
6484                              PawnAdvance += m; NrPawns++;
6485                     }
6486                     NrPieces += (p != EmptySquare);
6487                     NrW += ((int)p < (int)BlackPawn);
6488                     if(gameInfo.variant == VariantXiangqi && 
6489                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6490                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6491                         NrW -= ((int)p < (int)BlackPawn);
6492                     }
6493                 }
6494
6495                 /* Some material-based adjudications that have to be made before stalemate test */
6496                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6497                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6498                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6499                      if(canAdjudicate && appData.checkMates) {
6500                          if(engineOpponent)
6501                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6502                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6503                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6504                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6505                          return 1;
6506                      }
6507                 }
6508
6509                 /* Bare King in Shatranj (loses) or Losers (wins) */
6510                 if( NrW == 1 || NrPieces - NrW == 1) {
6511                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6512                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6513                      if(canAdjudicate && appData.checkMates) {
6514                          if(engineOpponent)
6515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6516                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6517                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6518                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6519                          return 1;
6520                      }
6521                   } else
6522                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6523                   {    /* bare King */
6524                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6525                         if(canAdjudicate && appData.checkMates) {
6526                             /* but only adjudicate if adjudication enabled */
6527                             if(engineOpponent)
6528                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6529                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6530                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6531                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6532                             return 1;
6533                         }
6534                   }
6535                 } else bare = 1;
6536
6537
6538             // don't wait for engine to announce game end if we can judge ourselves
6539             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6540               case MT_CHECK:
6541                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6542                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6543                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6544                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6545                             checkCnt++;
6546                         if(checkCnt >= 2) {
6547                             reason = "Xboard adjudication: 3rd check";
6548                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6549                             break;
6550                         }
6551                     }
6552                 }
6553               case MT_NONE:
6554               default:
6555                 break;
6556               case MT_STALEMATE:
6557               case MT_STAINMATE:
6558                 reason = "Xboard adjudication: Stalemate";
6559                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6560                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6561                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6562                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6563                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6564                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6565                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6566                                                                         EP_CHECKMATE : EP_WINS);
6567                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6568                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6569                 }
6570                 break;
6571               case MT_CHECKMATE:
6572                 reason = "Xboard adjudication: Checkmate";
6573                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6574                 break;
6575             }
6576
6577                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6578                     case EP_STALEMATE:
6579                         result = GameIsDrawn; break;
6580                     case EP_CHECKMATE:
6581                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6582                     case EP_WINS:
6583                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6584                     default:
6585                         result = (ChessMove) 0;
6586                 }
6587                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6588                     if(engineOpponent)
6589                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6590                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6591                     GameEnds( result, reason, GE_XBOARD );
6592                     return 1;
6593                 }
6594
6595                 /* Next absolutely insufficient mating material. */
6596                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6597                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6598                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6599                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6600                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6601
6602                      /* always flag draws, for judging claims */
6603                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6604
6605                      if(canAdjudicate && appData.materialDraws) {
6606                          /* but only adjudicate them if adjudication enabled */
6607                          if(engineOpponent) {
6608                            SendToProgram("force\n", engineOpponent); // suppress reply
6609                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6610                          }
6611                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6612                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6613                          return 1;
6614                      }
6615                 }
6616
6617                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6618                 if(NrPieces == 4 && 
6619                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6620                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6621                    || NrWN==2 || NrBN==2     /* KNNK */
6622                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6623                   ) ) {
6624                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6625                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6626                           if(engineOpponent) {
6627                             SendToProgram("force\n", engineOpponent); // suppress reply
6628                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6629                           }
6630                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6631                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6632                           return 1;
6633                      }
6634                 } else moveCount = 6;
6635             }
6636         }
6637           
6638         if (appData.debugMode) { int i;
6639             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6640                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6641                     appData.drawRepeats);
6642             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6643               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6644             
6645         }
6646
6647         // Repetition draws and 50-move rule can be applied independently of legality testing
6648
6649                 /* Check for rep-draws */
6650                 count = 0;
6651                 for(k = forwardMostMove-2;
6652                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6653                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6654                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6655                     k-=2)
6656                 {   int rights=0;
6657                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6658                         /* compare castling rights */
6659                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6660                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6661                                 rights++; /* King lost rights, while rook still had them */
6662                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6663                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6664                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6665                                    rights++; /* but at least one rook lost them */
6666                         }
6667                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6668                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6669                                 rights++; 
6670                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6671                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6672                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6673                                    rights++;
6674                         }
6675                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6676                             && appData.drawRepeats > 1) {
6677                              /* adjudicate after user-specified nr of repeats */
6678                              if(engineOpponent) {
6679                                SendToProgram("force\n", engineOpponent); // suppress reply
6680                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6681                              }
6682                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6683                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6684                                 // [HGM] xiangqi: check for forbidden perpetuals
6685                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6686                                 for(m=forwardMostMove; m>k; m-=2) {
6687                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6688                                         ourPerpetual = 0; // the current mover did not always check
6689                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6690                                         hisPerpetual = 0; // the opponent did not always check
6691                                 }
6692                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6693                                                                         ourPerpetual, hisPerpetual);
6694                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6695                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6696                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6697                                     return 1;
6698                                 }
6699                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6700                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6701                                 // Now check for perpetual chases
6702                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6703                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6704                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6705                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6706                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6707                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6708                                         return 1;
6709                                     }
6710                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6711                                         break; // Abort repetition-checking loop.
6712                                 }
6713                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6714                              }
6715                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6716                              return 1;
6717                         }
6718                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6719                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6720                     }
6721                 }
6722
6723                 /* Now we test for 50-move draws. Determine ply count */
6724                 count = forwardMostMove;
6725                 /* look for last irreversble move */
6726                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6727                     count--;
6728                 /* if we hit starting position, add initial plies */
6729                 if( count == backwardMostMove )
6730                     count -= initialRulePlies;
6731                 count = forwardMostMove - count; 
6732                 if( count >= 100)
6733                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6734                          /* this is used to judge if draw claims are legal */
6735                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6736                          if(engineOpponent) {
6737                            SendToProgram("force\n", engineOpponent); // suppress reply
6738                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6739                          }
6740                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6741                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6742                          return 1;
6743                 }
6744
6745                 /* if draw offer is pending, treat it as a draw claim
6746                  * when draw condition present, to allow engines a way to
6747                  * claim draws before making their move to avoid a race
6748                  * condition occurring after their move
6749                  */
6750                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6751                          char *p = NULL;
6752                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6753                              p = "Draw claim: 50-move rule";
6754                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6755                              p = "Draw claim: 3-fold repetition";
6756                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6757                              p = "Draw claim: insufficient mating material";
6758                          if( p != NULL && canAdjudicate) {
6759                              if(engineOpponent) {
6760                                SendToProgram("force\n", engineOpponent); // suppress reply
6761                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6762                              }
6763                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6764                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6765                              return 1;
6766                          }
6767                 }
6768
6769                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6770                     if(engineOpponent) {
6771                       SendToProgram("force\n", engineOpponent); // suppress reply
6772                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6773                     }
6774                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6775                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6776                     return 1;
6777                 }
6778         return 0;
6779 }
6780
6781 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6782 {   // [HGM] book: this routine intercepts moves to simulate book replies
6783     char *bookHit = NULL;
6784
6785     //first determine if the incoming move brings opponent into his book
6786     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6787         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6788     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6789     if(bookHit != NULL && !cps->bookSuspend) {
6790         // make sure opponent is not going to reply after receiving move to book position
6791         SendToProgram("force\n", cps);
6792         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6793     }
6794     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6795     // now arrange restart after book miss
6796     if(bookHit) {
6797         // after a book hit we never send 'go', and the code after the call to this routine
6798         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6799         char buf[MSG_SIZ];
6800         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6801         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6802         SendToProgram(buf, cps);
6803         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6804     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6805         SendToProgram("go\n", cps);
6806         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6807     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6808         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6809             SendToProgram("go\n", cps); 
6810         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6811     }
6812     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6813 }
6814
6815 char *savedMessage;
6816 ChessProgramState *savedState;
6817 void DeferredBookMove(void)
6818 {
6819         if(savedState->lastPing != savedState->lastPong)
6820                     ScheduleDelayedEvent(DeferredBookMove, 10);
6821         else
6822         HandleMachineMove(savedMessage, savedState);
6823 }
6824
6825 void
6826 HandleMachineMove(message, cps)
6827      char *message;
6828      ChessProgramState *cps;
6829 {
6830     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6831     char realname[MSG_SIZ];
6832     int fromX, fromY, toX, toY;
6833     ChessMove moveType;
6834     char promoChar;
6835     char *p;
6836     int machineWhite;
6837     char *bookHit;
6838
6839     cps->userError = 0;
6840
6841 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6842     /*
6843      * Kludge to ignore BEL characters
6844      */
6845     while (*message == '\007') message++;
6846
6847     /*
6848      * [HGM] engine debug message: ignore lines starting with '#' character
6849      */
6850     if(cps->debug && *message == '#') return;
6851
6852     /*
6853      * Look for book output
6854      */
6855     if (cps == &first && bookRequested) {
6856         if (message[0] == '\t' || message[0] == ' ') {
6857             /* Part of the book output is here; append it */
6858             strcat(bookOutput, message);
6859             strcat(bookOutput, "  \n");
6860             return;
6861         } else if (bookOutput[0] != NULLCHAR) {
6862             /* All of book output has arrived; display it */
6863             char *p = bookOutput;
6864             while (*p != NULLCHAR) {
6865                 if (*p == '\t') *p = ' ';
6866                 p++;
6867             }
6868             DisplayInformation(bookOutput);
6869             bookRequested = FALSE;
6870             /* Fall through to parse the current output */
6871         }
6872     }
6873
6874     /*
6875      * Look for machine move.
6876      */
6877     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6878         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6879     {
6880         /* This method is only useful on engines that support ping */
6881         if (cps->lastPing != cps->lastPong) {
6882           if (gameMode == BeginningOfGame) {
6883             /* Extra move from before last new; ignore */
6884             if (appData.debugMode) {
6885                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6886             }
6887           } else {
6888             if (appData.debugMode) {
6889                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6890                         cps->which, gameMode);
6891             }
6892
6893             SendToProgram("undo\n", cps);
6894           }
6895           return;
6896         }
6897
6898         switch (gameMode) {
6899           case BeginningOfGame:
6900             /* Extra move from before last reset; ignore */
6901             if (appData.debugMode) {
6902                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6903             }
6904             return;
6905
6906           case EndOfGame:
6907           case IcsIdle:
6908           default:
6909             /* Extra move after we tried to stop.  The mode test is
6910                not a reliable way of detecting this problem, but it's
6911                the best we can do on engines that don't support ping.
6912             */
6913             if (appData.debugMode) {
6914                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6915                         cps->which, gameMode);
6916             }
6917             SendToProgram("undo\n", cps);
6918             return;
6919
6920           case MachinePlaysWhite:
6921           case IcsPlayingWhite:
6922             machineWhite = TRUE;
6923             break;
6924
6925           case MachinePlaysBlack:
6926           case IcsPlayingBlack:
6927             machineWhite = FALSE;
6928             break;
6929
6930           case TwoMachinesPlay:
6931             machineWhite = (cps->twoMachinesColor[0] == 'w');
6932             break;
6933         }
6934         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6935             if (appData.debugMode) {
6936                 fprintf(debugFP,
6937                         "Ignoring move out of turn by %s, gameMode %d"
6938                         ", forwardMost %d\n",
6939                         cps->which, gameMode, forwardMostMove);
6940             }
6941             return;
6942         }
6943
6944     if (appData.debugMode) { int f = forwardMostMove;
6945         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6946                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6947                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6948     }
6949         if(cps->alphaRank) AlphaRank(machineMove, 4);
6950         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6951                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6952             /* Machine move could not be parsed; ignore it. */
6953             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6954                     machineMove, cps->which);
6955             DisplayError(buf1, 0);
6956             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6957                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6958             if (gameMode == TwoMachinesPlay) {
6959               GameEnds(machineWhite ? BlackWins : WhiteWins,
6960                        buf1, GE_XBOARD);
6961             }
6962             return;
6963         }
6964
6965         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6966         /* So we have to redo legality test with true e.p. status here,  */
6967         /* to make sure an illegal e.p. capture does not slip through,   */
6968         /* to cause a forfeit on a justified illegal-move complaint      */
6969         /* of the opponent.                                              */
6970         if( gameMode==TwoMachinesPlay && appData.testLegality
6971             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6972                                                               ) {
6973            ChessMove moveType;
6974            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6975                              fromY, fromX, toY, toX, promoChar);
6976             if (appData.debugMode) {
6977                 int i;
6978                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6979                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6980                 fprintf(debugFP, "castling rights\n");
6981             }
6982             if(moveType == IllegalMove) {
6983                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6984                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6985                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6986                            buf1, GE_XBOARD);
6987                 return;
6988            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6989            /* [HGM] Kludge to handle engines that send FRC-style castling
6990               when they shouldn't (like TSCP-Gothic) */
6991            switch(moveType) {
6992              case WhiteASideCastleFR:
6993              case BlackASideCastleFR:
6994                toX+=2;
6995                currentMoveString[2]++;
6996                break;
6997              case WhiteHSideCastleFR:
6998              case BlackHSideCastleFR:
6999                toX--;
7000                currentMoveString[2]--;
7001                break;
7002              default: ; // nothing to do, but suppresses warning of pedantic compilers
7003            }
7004         }
7005         hintRequested = FALSE;
7006         lastHint[0] = NULLCHAR;
7007         bookRequested = FALSE;
7008         /* Program may be pondering now */
7009         cps->maybeThinking = TRUE;
7010         if (cps->sendTime == 2) cps->sendTime = 1;
7011         if (cps->offeredDraw) cps->offeredDraw--;
7012
7013         /* currentMoveString is set as a side-effect of ParseOneMove */
7014         strcpy(machineMove, currentMoveString);
7015         strcat(machineMove, "\n");
7016         strcpy(moveList[forwardMostMove], machineMove);
7017
7018         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7019
7020         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7021         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7022             int count = 0;
7023
7024             while( count < adjudicateLossPlies ) {
7025                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7026
7027                 if( count & 1 ) {
7028                     score = -score; /* Flip score for winning side */
7029                 }
7030
7031                 if( score > adjudicateLossThreshold ) {
7032                     break;
7033                 }
7034
7035                 count++;
7036             }
7037
7038             if( count >= adjudicateLossPlies ) {
7039                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7040
7041                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7042                     "Xboard adjudication", 
7043                     GE_XBOARD );
7044
7045                 return;
7046             }
7047         }
7048
7049         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7050
7051 #if ZIPPY
7052         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7053             first.initDone) {
7054           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7055                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7056                 SendToICS("draw ");
7057                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7058           }
7059           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7060           ics_user_moved = 1;
7061           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7062                 char buf[3*MSG_SIZ];
7063
7064                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7065                         programStats.score / 100.,
7066                         programStats.depth,
7067                         programStats.time / 100.,
7068                         (unsigned int)programStats.nodes,
7069                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7070                         programStats.movelist);
7071                 SendToICS(buf);
7072 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7073           }
7074         }
7075 #endif
7076
7077         /* [AS] Save move info and clear stats for next move */
7078         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7079         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7080         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7081         ClearProgramStats();
7082         thinkOutput[0] = NULLCHAR;
7083         hiddenThinkOutputState = 0;
7084
7085         bookHit = NULL;
7086         if (gameMode == TwoMachinesPlay) {
7087             /* [HGM] relaying draw offers moved to after reception of move */
7088             /* and interpreting offer as claim if it brings draw condition */
7089             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7090                 SendToProgram("draw\n", cps->other);
7091             }
7092             if (cps->other->sendTime) {
7093                 SendTimeRemaining(cps->other,
7094                                   cps->other->twoMachinesColor[0] == 'w');
7095             }
7096             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7097             if (firstMove && !bookHit) {
7098                 firstMove = FALSE;
7099                 if (cps->other->useColors) {
7100                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7101                 }
7102                 SendToProgram("go\n", cps->other);
7103             }
7104             cps->other->maybeThinking = TRUE;
7105         }
7106
7107         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7108         
7109         if (!pausing && appData.ringBellAfterMoves) {
7110             RingBell();
7111         }
7112
7113         /* 
7114          * Reenable menu items that were disabled while
7115          * machine was thinking
7116          */
7117         if (gameMode != TwoMachinesPlay)
7118             SetUserThinkingEnables();
7119
7120         // [HGM] book: after book hit opponent has received move and is now in force mode
7121         // force the book reply into it, and then fake that it outputted this move by jumping
7122         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7123         if(bookHit) {
7124                 static char bookMove[MSG_SIZ]; // a bit generous?
7125
7126                 strcpy(bookMove, "move ");
7127                 strcat(bookMove, bookHit);
7128                 message = bookMove;
7129                 cps = cps->other;
7130                 programStats.nodes = programStats.depth = programStats.time = 
7131                 programStats.score = programStats.got_only_move = 0;
7132                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7133
7134                 if(cps->lastPing != cps->lastPong) {
7135                     savedMessage = message; // args for deferred call
7136                     savedState = cps;
7137                     ScheduleDelayedEvent(DeferredBookMove, 10);
7138                     return;
7139                 }
7140                 goto FakeBookMove;
7141         }
7142
7143         return;
7144     }
7145
7146     /* Set special modes for chess engines.  Later something general
7147      *  could be added here; for now there is just one kludge feature,
7148      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7149      *  when "xboard" is given as an interactive command.
7150      */
7151     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7152         cps->useSigint = FALSE;
7153         cps->useSigterm = FALSE;
7154     }
7155     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7156       ParseFeatures(message+8, cps);
7157       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7158     }
7159
7160     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7161      * want this, I was asked to put it in, and obliged.
7162      */
7163     if (!strncmp(message, "setboard ", 9)) {
7164         Board initial_position;
7165
7166         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7167
7168         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7169             DisplayError(_("Bad FEN received from engine"), 0);
7170             return ;
7171         } else {
7172            Reset(TRUE, FALSE);
7173            CopyBoard(boards[0], initial_position);
7174            initialRulePlies = FENrulePlies;
7175            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7176            else gameMode = MachinePlaysBlack;                 
7177            DrawPosition(FALSE, boards[currentMove]);
7178         }
7179         return;
7180     }
7181
7182     /*
7183      * Look for communication commands
7184      */
7185     if (!strncmp(message, "telluser ", 9)) {
7186         DisplayNote(message + 9);
7187         return;
7188     }
7189     if (!strncmp(message, "tellusererror ", 14)) {
7190         cps->userError = 1;
7191         DisplayError(message + 14, 0);
7192         return;
7193     }
7194     if (!strncmp(message, "tellopponent ", 13)) {
7195       if (appData.icsActive) {
7196         if (loggedOn) {
7197           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7198           SendToICS(buf1);
7199         }
7200       } else {
7201         DisplayNote(message + 13);
7202       }
7203       return;
7204     }
7205     if (!strncmp(message, "tellothers ", 11)) {
7206       if (appData.icsActive) {
7207         if (loggedOn) {
7208           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7209           SendToICS(buf1);
7210         }
7211       }
7212       return;
7213     }
7214     if (!strncmp(message, "tellall ", 8)) {
7215       if (appData.icsActive) {
7216         if (loggedOn) {
7217           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7218           SendToICS(buf1);
7219         }
7220       } else {
7221         DisplayNote(message + 8);
7222       }
7223       return;
7224     }
7225     if (strncmp(message, "warning", 7) == 0) {
7226         /* Undocumented feature, use tellusererror in new code */
7227         DisplayError(message, 0);
7228         return;
7229     }
7230     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7231         strcpy(realname, cps->tidy);
7232         strcat(realname, " query");
7233         AskQuestion(realname, buf2, buf1, cps->pr);
7234         return;
7235     }
7236     /* Commands from the engine directly to ICS.  We don't allow these to be 
7237      *  sent until we are logged on. Crafty kibitzes have been known to 
7238      *  interfere with the login process.
7239      */
7240     if (loggedOn) {
7241         if (!strncmp(message, "tellics ", 8)) {
7242             SendToICS(message + 8);
7243             SendToICS("\n");
7244             return;
7245         }
7246         if (!strncmp(message, "tellicsnoalias ", 15)) {
7247             SendToICS(ics_prefix);
7248             SendToICS(message + 15);
7249             SendToICS("\n");
7250             return;
7251         }
7252         /* The following are for backward compatibility only */
7253         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7254             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7255             SendToICS(ics_prefix);
7256             SendToICS(message);
7257             SendToICS("\n");
7258             return;
7259         }
7260     }
7261     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7262         return;
7263     }
7264     /*
7265      * If the move is illegal, cancel it and redraw the board.
7266      * Also deal with other error cases.  Matching is rather loose
7267      * here to accommodate engines written before the spec.
7268      */
7269     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7270         strncmp(message, "Error", 5) == 0) {
7271         if (StrStr(message, "name") || 
7272             StrStr(message, "rating") || StrStr(message, "?") ||
7273             StrStr(message, "result") || StrStr(message, "board") ||
7274             StrStr(message, "bk") || StrStr(message, "computer") ||
7275             StrStr(message, "variant") || StrStr(message, "hint") ||
7276             StrStr(message, "random") || StrStr(message, "depth") ||
7277             StrStr(message, "accepted")) {
7278             return;
7279         }
7280         if (StrStr(message, "protover")) {
7281           /* Program is responding to input, so it's apparently done
7282              initializing, and this error message indicates it is
7283              protocol version 1.  So we don't need to wait any longer
7284              for it to initialize and send feature commands. */
7285           FeatureDone(cps, 1);
7286           cps->protocolVersion = 1;
7287           return;
7288         }
7289         cps->maybeThinking = FALSE;
7290
7291         if (StrStr(message, "draw")) {
7292             /* Program doesn't have "draw" command */
7293             cps->sendDrawOffers = 0;
7294             return;
7295         }
7296         if (cps->sendTime != 1 &&
7297             (StrStr(message, "time") || StrStr(message, "otim"))) {
7298           /* Program apparently doesn't have "time" or "otim" command */
7299           cps->sendTime = 0;
7300           return;
7301         }
7302         if (StrStr(message, "analyze")) {
7303             cps->analysisSupport = FALSE;
7304             cps->analyzing = FALSE;
7305             Reset(FALSE, TRUE);
7306             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7307             DisplayError(buf2, 0);
7308             return;
7309         }
7310         if (StrStr(message, "(no matching move)st")) {
7311           /* Special kludge for GNU Chess 4 only */
7312           cps->stKludge = TRUE;
7313           SendTimeControl(cps, movesPerSession, timeControl,
7314                           timeIncrement, appData.searchDepth,
7315                           searchTime);
7316           return;
7317         }
7318         if (StrStr(message, "(no matching move)sd")) {
7319           /* Special kludge for GNU Chess 4 only */
7320           cps->sdKludge = TRUE;
7321           SendTimeControl(cps, movesPerSession, timeControl,
7322                           timeIncrement, appData.searchDepth,
7323                           searchTime);
7324           return;
7325         }
7326         if (!StrStr(message, "llegal")) {
7327             return;
7328         }
7329         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7330             gameMode == IcsIdle) return;
7331         if (forwardMostMove <= backwardMostMove) return;
7332         if (pausing) PauseEvent();
7333       if(appData.forceIllegal) {
7334             // [HGM] illegal: machine refused move; force position after move into it
7335           SendToProgram("force\n", cps);
7336           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7337                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7338                 // when black is to move, while there might be nothing on a2 or black
7339                 // might already have the move. So send the board as if white has the move.
7340                 // But first we must change the stm of the engine, as it refused the last move
7341                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7342                 if(WhiteOnMove(forwardMostMove)) {
7343                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7344                     SendBoard(cps, forwardMostMove); // kludgeless board
7345                 } else {
7346                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7347                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7348                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7349                 }
7350           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7351             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7352                  gameMode == TwoMachinesPlay)
7353               SendToProgram("go\n", cps);
7354             return;
7355       } else
7356         if (gameMode == PlayFromGameFile) {
7357             /* Stop reading this game file */
7358             gameMode = EditGame;
7359             ModeHighlight();
7360         }
7361         currentMove = forwardMostMove-1;
7362         DisplayMove(currentMove-1); /* before DisplayMoveError */
7363         SwitchClocks(forwardMostMove-1); // [HGM] race
7364         DisplayBothClocks();
7365         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7366                 parseList[currentMove], cps->which);
7367         DisplayMoveError(buf1);
7368         DrawPosition(FALSE, boards[currentMove]);
7369
7370         /* [HGM] illegal-move claim should forfeit game when Xboard */
7371         /* only passes fully legal moves                            */
7372         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7373             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7374                                 "False illegal-move claim", GE_XBOARD );
7375         }
7376         return;
7377     }
7378     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7379         /* Program has a broken "time" command that
7380            outputs a string not ending in newline.
7381            Don't use it. */
7382         cps->sendTime = 0;
7383     }
7384     
7385     /*
7386      * If chess program startup fails, exit with an error message.
7387      * Attempts to recover here are futile.
7388      */
7389     if ((StrStr(message, "unknown host") != NULL)
7390         || (StrStr(message, "No remote directory") != NULL)
7391         || (StrStr(message, "not found") != NULL)
7392         || (StrStr(message, "No such file") != NULL)
7393         || (StrStr(message, "can't alloc") != NULL)
7394         || (StrStr(message, "Permission denied") != NULL)) {
7395
7396         cps->maybeThinking = FALSE;
7397         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7398                 cps->which, cps->program, cps->host, message);
7399         RemoveInputSource(cps->isr);
7400         DisplayFatalError(buf1, 0, 1);
7401         return;
7402     }
7403     
7404     /* 
7405      * Look for hint output
7406      */
7407     if (sscanf(message, "Hint: %s", buf1) == 1) {
7408         if (cps == &first && hintRequested) {
7409             hintRequested = FALSE;
7410             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7411                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7412                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7413                                     PosFlags(forwardMostMove),
7414                                     fromY, fromX, toY, toX, promoChar, buf1);
7415                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7416                 DisplayInformation(buf2);
7417             } else {
7418                 /* Hint move could not be parsed!? */
7419               snprintf(buf2, sizeof(buf2),
7420                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7421                         buf1, cps->which);
7422                 DisplayError(buf2, 0);
7423             }
7424         } else {
7425             strcpy(lastHint, buf1);
7426         }
7427         return;
7428     }
7429
7430     /*
7431      * Ignore other messages if game is not in progress
7432      */
7433     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7434         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7435
7436     /*
7437      * look for win, lose, draw, or draw offer
7438      */
7439     if (strncmp(message, "1-0", 3) == 0) {
7440         char *p, *q, *r = "";
7441         p = strchr(message, '{');
7442         if (p) {
7443             q = strchr(p, '}');
7444             if (q) {
7445                 *q = NULLCHAR;
7446                 r = p + 1;
7447             }
7448         }
7449         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7450         return;
7451     } else if (strncmp(message, "0-1", 3) == 0) {
7452         char *p, *q, *r = "";
7453         p = strchr(message, '{');
7454         if (p) {
7455             q = strchr(p, '}');
7456             if (q) {
7457                 *q = NULLCHAR;
7458                 r = p + 1;
7459             }
7460         }
7461         /* Kludge for Arasan 4.1 bug */
7462         if (strcmp(r, "Black resigns") == 0) {
7463             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7464             return;
7465         }
7466         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7467         return;
7468     } else if (strncmp(message, "1/2", 3) == 0) {
7469         char *p, *q, *r = "";
7470         p = strchr(message, '{');
7471         if (p) {
7472             q = strchr(p, '}');
7473             if (q) {
7474                 *q = NULLCHAR;
7475                 r = p + 1;
7476             }
7477         }
7478             
7479         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7480         return;
7481
7482     } else if (strncmp(message, "White resign", 12) == 0) {
7483         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7484         return;
7485     } else if (strncmp(message, "Black resign", 12) == 0) {
7486         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7487         return;
7488     } else if (strncmp(message, "White matches", 13) == 0 ||
7489                strncmp(message, "Black matches", 13) == 0   ) {
7490         /* [HGM] ignore GNUShogi noises */
7491         return;
7492     } else if (strncmp(message, "White", 5) == 0 &&
7493                message[5] != '(' &&
7494                StrStr(message, "Black") == NULL) {
7495         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7496         return;
7497     } else if (strncmp(message, "Black", 5) == 0 &&
7498                message[5] != '(') {
7499         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7500         return;
7501     } else if (strcmp(message, "resign") == 0 ||
7502                strcmp(message, "computer resigns") == 0) {
7503         switch (gameMode) {
7504           case MachinePlaysBlack:
7505           case IcsPlayingBlack:
7506             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7507             break;
7508           case MachinePlaysWhite:
7509           case IcsPlayingWhite:
7510             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7511             break;
7512           case TwoMachinesPlay:
7513             if (cps->twoMachinesColor[0] == 'w')
7514               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7515             else
7516               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7517             break;
7518           default:
7519             /* can't happen */
7520             break;
7521         }
7522         return;
7523     } else if (strncmp(message, "opponent mates", 14) == 0) {
7524         switch (gameMode) {
7525           case MachinePlaysBlack:
7526           case IcsPlayingBlack:
7527             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7528             break;
7529           case MachinePlaysWhite:
7530           case IcsPlayingWhite:
7531             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7532             break;
7533           case TwoMachinesPlay:
7534             if (cps->twoMachinesColor[0] == 'w')
7535               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7536             else
7537               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7538             break;
7539           default:
7540             /* can't happen */
7541             break;
7542         }
7543         return;
7544     } else if (strncmp(message, "computer mates", 14) == 0) {
7545         switch (gameMode) {
7546           case MachinePlaysBlack:
7547           case IcsPlayingBlack:
7548             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7549             break;
7550           case MachinePlaysWhite:
7551           case IcsPlayingWhite:
7552             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7553             break;
7554           case TwoMachinesPlay:
7555             if (cps->twoMachinesColor[0] == 'w')
7556               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7557             else
7558               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7559             break;
7560           default:
7561             /* can't happen */
7562             break;
7563         }
7564         return;
7565     } else if (strncmp(message, "checkmate", 9) == 0) {
7566         if (WhiteOnMove(forwardMostMove)) {
7567             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7568         } else {
7569             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7570         }
7571         return;
7572     } else if (strstr(message, "Draw") != NULL ||
7573                strstr(message, "game is a draw") != NULL) {
7574         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7575         return;
7576     } else if (strstr(message, "offer") != NULL &&
7577                strstr(message, "draw") != NULL) {
7578 #if ZIPPY
7579         if (appData.zippyPlay && first.initDone) {
7580             /* Relay offer to ICS */
7581             SendToICS(ics_prefix);
7582             SendToICS("draw\n");
7583         }
7584 #endif
7585         cps->offeredDraw = 2; /* valid until this engine moves twice */
7586         if (gameMode == TwoMachinesPlay) {
7587             if (cps->other->offeredDraw) {
7588                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7589             /* [HGM] in two-machine mode we delay relaying draw offer      */
7590             /* until after we also have move, to see if it is really claim */
7591             }
7592         } else if (gameMode == MachinePlaysWhite ||
7593                    gameMode == MachinePlaysBlack) {
7594           if (userOfferedDraw) {
7595             DisplayInformation(_("Machine accepts your draw offer"));
7596             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7597           } else {
7598             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7599           }
7600         }
7601     }
7602
7603     
7604     /*
7605      * Look for thinking output
7606      */
7607     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7608           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7609                                 ) {
7610         int plylev, mvleft, mvtot, curscore, time;
7611         char mvname[MOVE_LEN];
7612         u64 nodes; // [DM]
7613         char plyext;
7614         int ignore = FALSE;
7615         int prefixHint = FALSE;
7616         mvname[0] = NULLCHAR;
7617
7618         switch (gameMode) {
7619           case MachinePlaysBlack:
7620           case IcsPlayingBlack:
7621             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7622             break;
7623           case MachinePlaysWhite:
7624           case IcsPlayingWhite:
7625             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7626             break;
7627           case AnalyzeMode:
7628           case AnalyzeFile:
7629             break;
7630           case IcsObserving: /* [DM] icsEngineAnalyze */
7631             if (!appData.icsEngineAnalyze) ignore = TRUE;
7632             break;
7633           case TwoMachinesPlay:
7634             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7635                 ignore = TRUE;
7636             }
7637             break;
7638           default:
7639             ignore = TRUE;
7640             break;
7641         }
7642
7643         if (!ignore) {
7644             buf1[0] = NULLCHAR;
7645             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7646                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7647
7648                 if (plyext != ' ' && plyext != '\t') {
7649                     time *= 100;
7650                 }
7651
7652                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7653                 if( cps->scoreIsAbsolute && 
7654                     ( gameMode == MachinePlaysBlack ||
7655                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7656                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7657                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7658                      !WhiteOnMove(currentMove)
7659                     ) )
7660                 {
7661                     curscore = -curscore;
7662                 }
7663
7664
7665                 programStats.depth = plylev;
7666                 programStats.nodes = nodes;
7667                 programStats.time = time;
7668                 programStats.score = curscore;
7669                 programStats.got_only_move = 0;
7670
7671                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7672                         int ticklen;
7673
7674                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7675                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7676                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7677                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7678                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7679                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7680                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7681                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7682                 }
7683
7684                 /* Buffer overflow protection */
7685                 if (buf1[0] != NULLCHAR) {
7686                     if (strlen(buf1) >= sizeof(programStats.movelist)
7687                         && appData.debugMode) {
7688                         fprintf(debugFP,
7689                                 "PV is too long; using the first %u bytes.\n",
7690                                 (unsigned) sizeof(programStats.movelist) - 1);
7691                     }
7692
7693                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7694                 } else {
7695                     sprintf(programStats.movelist, " no PV\n");
7696                 }
7697
7698                 if (programStats.seen_stat) {
7699                     programStats.ok_to_send = 1;
7700                 }
7701
7702                 if (strchr(programStats.movelist, '(') != NULL) {
7703                     programStats.line_is_book = 1;
7704                     programStats.nr_moves = 0;
7705                     programStats.moves_left = 0;
7706                 } else {
7707                     programStats.line_is_book = 0;
7708                 }
7709
7710                 SendProgramStatsToFrontend( cps, &programStats );
7711
7712                 /* 
7713                     [AS] Protect the thinkOutput buffer from overflow... this
7714                     is only useful if buf1 hasn't overflowed first!
7715                 */
7716                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7717                         plylev, 
7718                         (gameMode == TwoMachinesPlay ?
7719                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7720                         ((double) curscore) / 100.0,
7721                         prefixHint ? lastHint : "",
7722                         prefixHint ? " " : "" );
7723
7724                 if( buf1[0] != NULLCHAR ) {
7725                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7726
7727                     if( strlen(buf1) > max_len ) {
7728                         if( appData.debugMode) {
7729                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7730                         }
7731                         buf1[max_len+1] = '\0';
7732                     }
7733
7734                     strcat( thinkOutput, buf1 );
7735                 }
7736
7737                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7738                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7739                     DisplayMove(currentMove - 1);
7740                 }
7741                 return;
7742
7743             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7744                 /* crafty (9.25+) says "(only move) <move>"
7745                  * if there is only 1 legal move
7746                  */
7747                 sscanf(p, "(only move) %s", buf1);
7748                 sprintf(thinkOutput, "%s (only move)", buf1);
7749                 sprintf(programStats.movelist, "%s (only move)", buf1);
7750                 programStats.depth = 1;
7751                 programStats.nr_moves = 1;
7752                 programStats.moves_left = 1;
7753                 programStats.nodes = 1;
7754                 programStats.time = 1;
7755                 programStats.got_only_move = 1;
7756
7757                 /* Not really, but we also use this member to
7758                    mean "line isn't going to change" (Crafty
7759                    isn't searching, so stats won't change) */
7760                 programStats.line_is_book = 1;
7761
7762                 SendProgramStatsToFrontend( cps, &programStats );
7763                 
7764                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7765                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7766                     DisplayMove(currentMove - 1);
7767                 }
7768                 return;
7769             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7770                               &time, &nodes, &plylev, &mvleft,
7771                               &mvtot, mvname) >= 5) {
7772                 /* The stat01: line is from Crafty (9.29+) in response
7773                    to the "." command */
7774                 programStats.seen_stat = 1;
7775                 cps->maybeThinking = TRUE;
7776
7777                 if (programStats.got_only_move || !appData.periodicUpdates)
7778                   return;
7779
7780                 programStats.depth = plylev;
7781                 programStats.time = time;
7782                 programStats.nodes = nodes;
7783                 programStats.moves_left = mvleft;
7784                 programStats.nr_moves = mvtot;
7785                 strcpy(programStats.move_name, mvname);
7786                 programStats.ok_to_send = 1;
7787                 programStats.movelist[0] = '\0';
7788
7789                 SendProgramStatsToFrontend( cps, &programStats );
7790
7791                 return;
7792
7793             } else if (strncmp(message,"++",2) == 0) {
7794                 /* Crafty 9.29+ outputs this */
7795                 programStats.got_fail = 2;
7796                 return;
7797
7798             } else if (strncmp(message,"--",2) == 0) {
7799                 /* Crafty 9.29+ outputs this */
7800                 programStats.got_fail = 1;
7801                 return;
7802
7803             } else if (thinkOutput[0] != NULLCHAR &&
7804                        strncmp(message, "    ", 4) == 0) {
7805                 unsigned message_len;
7806
7807                 p = message;
7808                 while (*p && *p == ' ') p++;
7809
7810                 message_len = strlen( p );
7811
7812                 /* [AS] Avoid buffer overflow */
7813                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7814                     strcat(thinkOutput, " ");
7815                     strcat(thinkOutput, p);
7816                 }
7817
7818                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7819                     strcat(programStats.movelist, " ");
7820                     strcat(programStats.movelist, p);
7821                 }
7822
7823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7825                     DisplayMove(currentMove - 1);
7826                 }
7827                 return;
7828             }
7829         }
7830         else {
7831             buf1[0] = NULLCHAR;
7832
7833             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7834                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7835             {
7836                 ChessProgramStats cpstats;
7837
7838                 if (plyext != ' ' && plyext != '\t') {
7839                     time *= 100;
7840                 }
7841
7842                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7843                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7844                     curscore = -curscore;
7845                 }
7846
7847                 cpstats.depth = plylev;
7848                 cpstats.nodes = nodes;
7849                 cpstats.time = time;
7850                 cpstats.score = curscore;
7851                 cpstats.got_only_move = 0;
7852                 cpstats.movelist[0] = '\0';
7853
7854                 if (buf1[0] != NULLCHAR) {
7855                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7856                 }
7857
7858                 cpstats.ok_to_send = 0;
7859                 cpstats.line_is_book = 0;
7860                 cpstats.nr_moves = 0;
7861                 cpstats.moves_left = 0;
7862
7863                 SendProgramStatsToFrontend( cps, &cpstats );
7864             }
7865         }
7866     }
7867 }
7868
7869
7870 /* Parse a game score from the character string "game", and
7871    record it as the history of the current game.  The game
7872    score is NOT assumed to start from the standard position. 
7873    The display is not updated in any way.
7874    */
7875 void
7876 ParseGameHistory(game)
7877      char *game;
7878 {
7879     ChessMove moveType;
7880     int fromX, fromY, toX, toY, boardIndex;
7881     char promoChar;
7882     char *p, *q;
7883     char buf[MSG_SIZ];
7884
7885     if (appData.debugMode)
7886       fprintf(debugFP, "Parsing game history: %s\n", game);
7887
7888     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7889     gameInfo.site = StrSave(appData.icsHost);
7890     gameInfo.date = PGNDate();
7891     gameInfo.round = StrSave("-");
7892
7893     /* Parse out names of players */
7894     while (*game == ' ') game++;
7895     p = buf;
7896     while (*game != ' ') *p++ = *game++;
7897     *p = NULLCHAR;
7898     gameInfo.white = StrSave(buf);
7899     while (*game == ' ') game++;
7900     p = buf;
7901     while (*game != ' ' && *game != '\n') *p++ = *game++;
7902     *p = NULLCHAR;
7903     gameInfo.black = StrSave(buf);
7904
7905     /* Parse moves */
7906     boardIndex = blackPlaysFirst ? 1 : 0;
7907     yynewstr(game);
7908     for (;;) {
7909         yyboardindex = boardIndex;
7910         moveType = (ChessMove) yylex();
7911         switch (moveType) {
7912           case IllegalMove:             /* maybe suicide chess, etc. */
7913   if (appData.debugMode) {
7914     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7915     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7916     setbuf(debugFP, NULL);
7917   }
7918           case WhitePromotionChancellor:
7919           case BlackPromotionChancellor:
7920           case WhitePromotionArchbishop:
7921           case BlackPromotionArchbishop:
7922           case WhitePromotionQueen:
7923           case BlackPromotionQueen:
7924           case WhitePromotionRook:
7925           case BlackPromotionRook:
7926           case WhitePromotionBishop:
7927           case BlackPromotionBishop:
7928           case WhitePromotionKnight:
7929           case BlackPromotionKnight:
7930           case WhitePromotionKing:
7931           case BlackPromotionKing:
7932           case NormalMove:
7933           case WhiteCapturesEnPassant:
7934           case BlackCapturesEnPassant:
7935           case WhiteKingSideCastle:
7936           case WhiteQueenSideCastle:
7937           case BlackKingSideCastle:
7938           case BlackQueenSideCastle:
7939           case WhiteKingSideCastleWild:
7940           case WhiteQueenSideCastleWild:
7941           case BlackKingSideCastleWild:
7942           case BlackQueenSideCastleWild:
7943           /* PUSH Fabien */
7944           case WhiteHSideCastleFR:
7945           case WhiteASideCastleFR:
7946           case BlackHSideCastleFR:
7947           case BlackASideCastleFR:
7948           /* POP Fabien */
7949             fromX = currentMoveString[0] - AAA;
7950             fromY = currentMoveString[1] - ONE;
7951             toX = currentMoveString[2] - AAA;
7952             toY = currentMoveString[3] - ONE;
7953             promoChar = currentMoveString[4];
7954             break;
7955           case WhiteDrop:
7956           case BlackDrop:
7957             fromX = moveType == WhiteDrop ?
7958               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7959             (int) CharToPiece(ToLower(currentMoveString[0]));
7960             fromY = DROP_RANK;
7961             toX = currentMoveString[2] - AAA;
7962             toY = currentMoveString[3] - ONE;
7963             promoChar = NULLCHAR;
7964             break;
7965           case AmbiguousMove:
7966             /* bug? */
7967             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7968   if (appData.debugMode) {
7969     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7970     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7971     setbuf(debugFP, NULL);
7972   }
7973             DisplayError(buf, 0);
7974             return;
7975           case ImpossibleMove:
7976             /* bug? */
7977             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7978   if (appData.debugMode) {
7979     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7980     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7981     setbuf(debugFP, NULL);
7982   }
7983             DisplayError(buf, 0);
7984             return;
7985           case (ChessMove) 0:   /* end of file */
7986             if (boardIndex < backwardMostMove) {
7987                 /* Oops, gap.  How did that happen? */
7988                 DisplayError(_("Gap in move list"), 0);
7989                 return;
7990             }
7991             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7992             if (boardIndex > forwardMostMove) {
7993                 forwardMostMove = boardIndex;
7994             }
7995             return;
7996           case ElapsedTime:
7997             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7998                 strcat(parseList[boardIndex-1], " ");
7999                 strcat(parseList[boardIndex-1], yy_text);
8000             }
8001             continue;
8002           case Comment:
8003           case PGNTag:
8004           case NAG:
8005           default:
8006             /* ignore */
8007             continue;
8008           case WhiteWins:
8009           case BlackWins:
8010           case GameIsDrawn:
8011           case GameUnfinished:
8012             if (gameMode == IcsExamining) {
8013                 if (boardIndex < backwardMostMove) {
8014                     /* Oops, gap.  How did that happen? */
8015                     return;
8016                 }
8017                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8018                 return;
8019             }
8020             gameInfo.result = moveType;
8021             p = strchr(yy_text, '{');
8022             if (p == NULL) p = strchr(yy_text, '(');
8023             if (p == NULL) {
8024                 p = yy_text;
8025                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8026             } else {
8027                 q = strchr(p, *p == '{' ? '}' : ')');
8028                 if (q != NULL) *q = NULLCHAR;
8029                 p++;
8030             }
8031             gameInfo.resultDetails = StrSave(p);
8032             continue;
8033         }
8034         if (boardIndex >= forwardMostMove &&
8035             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8036             backwardMostMove = blackPlaysFirst ? 1 : 0;
8037             return;
8038         }
8039         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8040                                  fromY, fromX, toY, toX, promoChar,
8041                                  parseList[boardIndex]);
8042         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8043         /* currentMoveString is set as a side-effect of yylex */
8044         strcpy(moveList[boardIndex], currentMoveString);
8045         strcat(moveList[boardIndex], "\n");
8046         boardIndex++;
8047         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8048         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8049           case MT_NONE:
8050           case MT_STALEMATE:
8051           default:
8052             break;
8053           case MT_CHECK:
8054             if(gameInfo.variant != VariantShogi)
8055                 strcat(parseList[boardIndex - 1], "+");
8056             break;
8057           case MT_CHECKMATE:
8058           case MT_STAINMATE:
8059             strcat(parseList[boardIndex - 1], "#");
8060             break;
8061         }
8062     }
8063 }
8064
8065
8066 /* Apply a move to the given board  */
8067 void
8068 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8069      int fromX, fromY, toX, toY;
8070      int promoChar;
8071      Board board;
8072 {
8073   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8074   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8075
8076     /* [HGM] compute & store e.p. status and castling rights for new position */
8077     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8078     { int i;
8079
8080       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8081       oldEP = (signed char)board[EP_STATUS];
8082       board[EP_STATUS] = EP_NONE;
8083
8084       if( board[toY][toX] != EmptySquare ) 
8085            board[EP_STATUS] = EP_CAPTURE;  
8086
8087       if( board[fromY][fromX] == WhitePawn ) {
8088            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8089                board[EP_STATUS] = EP_PAWN_MOVE;
8090            if( toY-fromY==2) {
8091                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8092                         gameInfo.variant != VariantBerolina || toX < fromX)
8093                       board[EP_STATUS] = toX | berolina;
8094                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8095                         gameInfo.variant != VariantBerolina || toX > fromX) 
8096                       board[EP_STATUS] = toX;
8097            }
8098       } else 
8099       if( board[fromY][fromX] == BlackPawn ) {
8100            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8101                board[EP_STATUS] = EP_PAWN_MOVE; 
8102            if( toY-fromY== -2) {
8103                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8104                         gameInfo.variant != VariantBerolina || toX < fromX)
8105                       board[EP_STATUS] = toX | berolina;
8106                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8107                         gameInfo.variant != VariantBerolina || toX > fromX) 
8108                       board[EP_STATUS] = toX;
8109            }
8110        }
8111
8112        for(i=0; i<nrCastlingRights; i++) {
8113            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8114               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8115              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8116        }
8117
8118     }
8119
8120   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8121   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8122        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8123          
8124   if (fromX == toX && fromY == toY) return;
8125
8126   if (fromY == DROP_RANK) {
8127         /* must be first */
8128         piece = board[toY][toX] = (ChessSquare) fromX;
8129   } else {
8130      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8131      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8132      if(gameInfo.variant == VariantKnightmate)
8133          king += (int) WhiteUnicorn - (int) WhiteKing;
8134
8135     /* Code added by Tord: */
8136     /* FRC castling assumed when king captures friendly rook. */
8137     if (board[fromY][fromX] == WhiteKing &&
8138              board[toY][toX] == WhiteRook) {
8139       board[fromY][fromX] = EmptySquare;
8140       board[toY][toX] = EmptySquare;
8141       if(toX > fromX) {
8142         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8143       } else {
8144         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8145       }
8146     } else if (board[fromY][fromX] == BlackKing &&
8147                board[toY][toX] == BlackRook) {
8148       board[fromY][fromX] = EmptySquare;
8149       board[toY][toX] = EmptySquare;
8150       if(toX > fromX) {
8151         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8152       } else {
8153         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8154       }
8155     /* End of code added by Tord */
8156
8157     } else if (board[fromY][fromX] == king
8158         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8159         && toY == fromY && toX > fromX+1) {
8160         board[fromY][fromX] = EmptySquare;
8161         board[toY][toX] = king;
8162         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8163         board[fromY][BOARD_RGHT-1] = EmptySquare;
8164     } else if (board[fromY][fromX] == king
8165         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8166                && toY == fromY && toX < fromX-1) {
8167         board[fromY][fromX] = EmptySquare;
8168         board[toY][toX] = king;
8169         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8170         board[fromY][BOARD_LEFT] = EmptySquare;
8171     } else if (board[fromY][fromX] == WhitePawn
8172                && toY >= BOARD_HEIGHT-promoRank
8173                && gameInfo.variant != VariantXiangqi
8174                ) {
8175         /* white pawn promotion */
8176         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8177         if (board[toY][toX] == EmptySquare) {
8178             board[toY][toX] = WhiteQueen;
8179         }
8180         if(gameInfo.variant==VariantBughouse ||
8181            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8182             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8183         board[fromY][fromX] = EmptySquare;
8184     } else if ((fromY == BOARD_HEIGHT-4)
8185                && (toX != fromX)
8186                && gameInfo.variant != VariantXiangqi
8187                && gameInfo.variant != VariantBerolina
8188                && (board[fromY][fromX] == WhitePawn)
8189                && (board[toY][toX] == EmptySquare)) {
8190         board[fromY][fromX] = EmptySquare;
8191         board[toY][toX] = WhitePawn;
8192         captured = board[toY - 1][toX];
8193         board[toY - 1][toX] = EmptySquare;
8194     } else if ((fromY == BOARD_HEIGHT-4)
8195                && (toX == fromX)
8196                && gameInfo.variant == VariantBerolina
8197                && (board[fromY][fromX] == WhitePawn)
8198                && (board[toY][toX] == EmptySquare)) {
8199         board[fromY][fromX] = EmptySquare;
8200         board[toY][toX] = WhitePawn;
8201         if(oldEP & EP_BEROLIN_A) {
8202                 captured = board[fromY][fromX-1];
8203                 board[fromY][fromX-1] = EmptySquare;
8204         }else{  captured = board[fromY][fromX+1];
8205                 board[fromY][fromX+1] = EmptySquare;
8206         }
8207     } else if (board[fromY][fromX] == king
8208         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8209                && toY == fromY && toX > fromX+1) {
8210         board[fromY][fromX] = EmptySquare;
8211         board[toY][toX] = king;
8212         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8213         board[fromY][BOARD_RGHT-1] = EmptySquare;
8214     } else if (board[fromY][fromX] == king
8215         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8216                && toY == fromY && toX < fromX-1) {
8217         board[fromY][fromX] = EmptySquare;
8218         board[toY][toX] = king;
8219         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8220         board[fromY][BOARD_LEFT] = EmptySquare;
8221     } else if (fromY == 7 && fromX == 3
8222                && board[fromY][fromX] == BlackKing
8223                && toY == 7 && toX == 5) {
8224         board[fromY][fromX] = EmptySquare;
8225         board[toY][toX] = BlackKing;
8226         board[fromY][7] = EmptySquare;
8227         board[toY][4] = BlackRook;
8228     } else if (fromY == 7 && fromX == 3
8229                && board[fromY][fromX] == BlackKing
8230                && toY == 7 && toX == 1) {
8231         board[fromY][fromX] = EmptySquare;
8232         board[toY][toX] = BlackKing;
8233         board[fromY][0] = EmptySquare;
8234         board[toY][2] = BlackRook;
8235     } else if (board[fromY][fromX] == BlackPawn
8236                && toY < promoRank
8237                && gameInfo.variant != VariantXiangqi
8238                ) {
8239         /* black pawn promotion */
8240         board[toY][toX] = CharToPiece(ToLower(promoChar));
8241         if (board[toY][toX] == EmptySquare) {
8242             board[toY][toX] = BlackQueen;
8243         }
8244         if(gameInfo.variant==VariantBughouse ||
8245            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8246             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8247         board[fromY][fromX] = EmptySquare;
8248     } else if ((fromY == 3)
8249                && (toX != fromX)
8250                && gameInfo.variant != VariantXiangqi
8251                && gameInfo.variant != VariantBerolina
8252                && (board[fromY][fromX] == BlackPawn)
8253                && (board[toY][toX] == EmptySquare)) {
8254         board[fromY][fromX] = EmptySquare;
8255         board[toY][toX] = BlackPawn;
8256         captured = board[toY + 1][toX];
8257         board[toY + 1][toX] = EmptySquare;
8258     } else if ((fromY == 3)
8259                && (toX == fromX)
8260                && gameInfo.variant == VariantBerolina
8261                && (board[fromY][fromX] == BlackPawn)
8262                && (board[toY][toX] == EmptySquare)) {
8263         board[fromY][fromX] = EmptySquare;
8264         board[toY][toX] = BlackPawn;
8265         if(oldEP & EP_BEROLIN_A) {
8266                 captured = board[fromY][fromX-1];
8267                 board[fromY][fromX-1] = EmptySquare;
8268         }else{  captured = board[fromY][fromX+1];
8269                 board[fromY][fromX+1] = EmptySquare;
8270         }
8271     } else {
8272         board[toY][toX] = board[fromY][fromX];
8273         board[fromY][fromX] = EmptySquare;
8274     }
8275
8276     /* [HGM] now we promote for Shogi, if needed */
8277     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8278         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8279   }
8280
8281     if (gameInfo.holdingsWidth != 0) {
8282
8283       /* !!A lot more code needs to be written to support holdings  */
8284       /* [HGM] OK, so I have written it. Holdings are stored in the */
8285       /* penultimate board files, so they are automaticlly stored   */
8286       /* in the game history.                                       */
8287       if (fromY == DROP_RANK) {
8288         /* Delete from holdings, by decreasing count */
8289         /* and erasing image if necessary            */
8290         p = (int) fromX;
8291         if(p < (int) BlackPawn) { /* white drop */
8292              p -= (int)WhitePawn;
8293                  p = PieceToNumber((ChessSquare)p);
8294              if(p >= gameInfo.holdingsSize) p = 0;
8295              if(--board[p][BOARD_WIDTH-2] <= 0)
8296                   board[p][BOARD_WIDTH-1] = EmptySquare;
8297              if((int)board[p][BOARD_WIDTH-2] < 0)
8298                         board[p][BOARD_WIDTH-2] = 0;
8299         } else {                  /* black drop */
8300              p -= (int)BlackPawn;
8301                  p = PieceToNumber((ChessSquare)p);
8302              if(p >= gameInfo.holdingsSize) p = 0;
8303              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8304                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8305              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8306                         board[BOARD_HEIGHT-1-p][1] = 0;
8307         }
8308       }
8309       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8310           && gameInfo.variant != VariantBughouse        ) {
8311         /* [HGM] holdings: Add to holdings, if holdings exist */
8312         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8313                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8314                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8315         }
8316         p = (int) captured;
8317         if (p >= (int) BlackPawn) {
8318           p -= (int)BlackPawn;
8319           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8320                   /* in Shogi restore piece to its original  first */
8321                   captured = (ChessSquare) (DEMOTED captured);
8322                   p = DEMOTED p;
8323           }
8324           p = PieceToNumber((ChessSquare)p);
8325           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8326           board[p][BOARD_WIDTH-2]++;
8327           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8328         } else {
8329           p -= (int)WhitePawn;
8330           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8331                   captured = (ChessSquare) (DEMOTED captured);
8332                   p = DEMOTED p;
8333           }
8334           p = PieceToNumber((ChessSquare)p);
8335           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8336           board[BOARD_HEIGHT-1-p][1]++;
8337           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8338         }
8339       }
8340     } else if (gameInfo.variant == VariantAtomic) {
8341       if (captured != EmptySquare) {
8342         int y, x;
8343         for (y = toY-1; y <= toY+1; y++) {
8344           for (x = toX-1; x <= toX+1; x++) {
8345             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8346                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8347               board[y][x] = EmptySquare;
8348             }
8349           }
8350         }
8351         board[toY][toX] = EmptySquare;
8352       }
8353     }
8354     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8355         /* [HGM] Shogi promotions */
8356         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8357     }
8358
8359     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8360                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8361         // [HGM] superchess: take promotion piece out of holdings
8362         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8363         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8364             if(!--board[k][BOARD_WIDTH-2])
8365                 board[k][BOARD_WIDTH-1] = EmptySquare;
8366         } else {
8367             if(!--board[BOARD_HEIGHT-1-k][1])
8368                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8369         }
8370     }
8371
8372 }
8373
8374 /* Updates forwardMostMove */
8375 void
8376 MakeMove(fromX, fromY, toX, toY, promoChar)
8377      int fromX, fromY, toX, toY;
8378      int promoChar;
8379 {
8380 //    forwardMostMove++; // [HGM] bare: moved downstream
8381
8382     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8383         int timeLeft; static int lastLoadFlag=0; int king, piece;
8384         piece = boards[forwardMostMove][fromY][fromX];
8385         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8386         if(gameInfo.variant == VariantKnightmate)
8387             king += (int) WhiteUnicorn - (int) WhiteKing;
8388         if(forwardMostMove == 0) {
8389             if(blackPlaysFirst) 
8390                 fprintf(serverMoves, "%s;", second.tidy);
8391             fprintf(serverMoves, "%s;", first.tidy);
8392             if(!blackPlaysFirst) 
8393                 fprintf(serverMoves, "%s;", second.tidy);
8394         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8395         lastLoadFlag = loadFlag;
8396         // print base move
8397         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8398         // print castling suffix
8399         if( toY == fromY && piece == king ) {
8400             if(toX-fromX > 1)
8401                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8402             if(fromX-toX >1)
8403                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8404         }
8405         // e.p. suffix
8406         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8407              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8408              boards[forwardMostMove][toY][toX] == EmptySquare
8409              && fromX != toX )
8410                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8411         // promotion suffix
8412         if(promoChar != NULLCHAR)
8413                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8414         if(!loadFlag) {
8415             fprintf(serverMoves, "/%d/%d",
8416                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8417             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8418             else                      timeLeft = blackTimeRemaining/1000;
8419             fprintf(serverMoves, "/%d", timeLeft);
8420         }
8421         fflush(serverMoves);
8422     }
8423
8424     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8425       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8426                         0, 1);
8427       return;
8428     }
8429     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8430     if (commentList[forwardMostMove+1] != NULL) {
8431         free(commentList[forwardMostMove+1]);
8432         commentList[forwardMostMove+1] = NULL;
8433     }
8434     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8435     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8436     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8437     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8438     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8439     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8440     gameInfo.result = GameUnfinished;
8441     if (gameInfo.resultDetails != NULL) {
8442         free(gameInfo.resultDetails);
8443         gameInfo.resultDetails = NULL;
8444     }
8445     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8446                               moveList[forwardMostMove - 1]);
8447     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8448                              PosFlags(forwardMostMove - 1),
8449                              fromY, fromX, toY, toX, promoChar,
8450                              parseList[forwardMostMove - 1]);
8451     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8452       case MT_NONE:
8453       case MT_STALEMATE:
8454       default:
8455         break;
8456       case MT_CHECK:
8457         if(gameInfo.variant != VariantShogi)
8458             strcat(parseList[forwardMostMove - 1], "+");
8459         break;
8460       case MT_CHECKMATE:
8461       case MT_STAINMATE:
8462         strcat(parseList[forwardMostMove - 1], "#");
8463         break;
8464     }
8465     if (appData.debugMode) {
8466         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8467     }
8468
8469 }
8470
8471 /* Updates currentMove if not pausing */
8472 void
8473 ShowMove(fromX, fromY, toX, toY)
8474 {
8475     int instant = (gameMode == PlayFromGameFile) ?
8476         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8477     if(appData.noGUI) return;
8478     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8479         if (!instant) {
8480             if (forwardMostMove == currentMove + 1) {
8481                 AnimateMove(boards[forwardMostMove - 1],
8482                             fromX, fromY, toX, toY);
8483             }
8484             if (appData.highlightLastMove) {
8485                 SetHighlights(fromX, fromY, toX, toY);
8486             }
8487         }
8488         currentMove = forwardMostMove;
8489     }
8490
8491     if (instant) return;
8492
8493     DisplayMove(currentMove - 1);
8494     DrawPosition(FALSE, boards[currentMove]);
8495     DisplayBothClocks();
8496     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8497 }
8498
8499 void SendEgtPath(ChessProgramState *cps)
8500 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8501         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8502
8503         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8504
8505         while(*p) {
8506             char c, *q = name+1, *r, *s;
8507
8508             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8509             while(*p && *p != ',') *q++ = *p++;
8510             *q++ = ':'; *q = 0;
8511             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8512                 strcmp(name, ",nalimov:") == 0 ) {
8513                 // take nalimov path from the menu-changeable option first, if it is defined
8514                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8515                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8516             } else
8517             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8518                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8519                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8520                 s = r = StrStr(s, ":") + 1; // beginning of path info
8521                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8522                 c = *r; *r = 0;             // temporarily null-terminate path info
8523                     *--q = 0;               // strip of trailig ':' from name
8524                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8525                 *r = c;
8526                 SendToProgram(buf,cps);     // send egtbpath command for this format
8527             }
8528             if(*p == ',') p++; // read away comma to position for next format name
8529         }
8530 }
8531
8532 void
8533 InitChessProgram(cps, setup)
8534      ChessProgramState *cps;
8535      int setup; /* [HGM] needed to setup FRC opening position */
8536 {
8537     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8538     if (appData.noChessProgram) return;
8539     hintRequested = FALSE;
8540     bookRequested = FALSE;
8541
8542     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8543     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8544     if(cps->memSize) { /* [HGM] memory */
8545         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8546         SendToProgram(buf, cps);
8547     }
8548     SendEgtPath(cps); /* [HGM] EGT */
8549     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8550         sprintf(buf, "cores %d\n", appData.smpCores);
8551         SendToProgram(buf, cps);
8552     }
8553
8554     SendToProgram(cps->initString, cps);
8555     if (gameInfo.variant != VariantNormal &&
8556         gameInfo.variant != VariantLoadable
8557         /* [HGM] also send variant if board size non-standard */
8558         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8559                                             ) {
8560       char *v = VariantName(gameInfo.variant);
8561       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8562         /* [HGM] in protocol 1 we have to assume all variants valid */
8563         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8564         DisplayFatalError(buf, 0, 1);
8565         return;
8566       }
8567
8568       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8569       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8570       if( gameInfo.variant == VariantXiangqi )
8571            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8572       if( gameInfo.variant == VariantShogi )
8573            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8574       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8575            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8576       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8577                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8578            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8579       if( gameInfo.variant == VariantCourier )
8580            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8581       if( gameInfo.variant == VariantSuper )
8582            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8583       if( gameInfo.variant == VariantGreat )
8584            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8585
8586       if(overruled) {
8587            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8588                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8589            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8590            if(StrStr(cps->variants, b) == NULL) { 
8591                // specific sized variant not known, check if general sizing allowed
8592                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8593                    if(StrStr(cps->variants, "boardsize") == NULL) {
8594                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8595                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8596                        DisplayFatalError(buf, 0, 1);
8597                        return;
8598                    }
8599                    /* [HGM] here we really should compare with the maximum supported board size */
8600                }
8601            }
8602       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8603       sprintf(buf, "variant %s\n", b);
8604       SendToProgram(buf, cps);
8605     }
8606     currentlyInitializedVariant = gameInfo.variant;
8607
8608     /* [HGM] send opening position in FRC to first engine */
8609     if(setup) {
8610           SendToProgram("force\n", cps);
8611           SendBoard(cps, 0);
8612           /* engine is now in force mode! Set flag to wake it up after first move. */
8613           setboardSpoiledMachineBlack = 1;
8614     }
8615
8616     if (cps->sendICS) {
8617       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8618       SendToProgram(buf, cps);
8619     }
8620     cps->maybeThinking = FALSE;
8621     cps->offeredDraw = 0;
8622     if (!appData.icsActive) {
8623         SendTimeControl(cps, movesPerSession, timeControl,
8624                         timeIncrement, appData.searchDepth,
8625                         searchTime);
8626     }
8627     if (appData.showThinking 
8628         // [HGM] thinking: four options require thinking output to be sent
8629         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8630                                 ) {
8631         SendToProgram("post\n", cps);
8632     }
8633     SendToProgram("hard\n", cps);
8634     if (!appData.ponderNextMove) {
8635         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8636            it without being sure what state we are in first.  "hard"
8637            is not a toggle, so that one is OK.
8638          */
8639         SendToProgram("easy\n", cps);
8640     }
8641     if (cps->usePing) {
8642       sprintf(buf, "ping %d\n", ++cps->lastPing);
8643       SendToProgram(buf, cps);
8644     }
8645     cps->initDone = TRUE;
8646 }   
8647
8648
8649 void
8650 StartChessProgram(cps)
8651      ChessProgramState *cps;
8652 {
8653     char buf[MSG_SIZ];
8654     int err;
8655
8656     if (appData.noChessProgram) return;
8657     cps->initDone = FALSE;
8658
8659     if (strcmp(cps->host, "localhost") == 0) {
8660         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8661     } else if (*appData.remoteShell == NULLCHAR) {
8662         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8663     } else {
8664         if (*appData.remoteUser == NULLCHAR) {
8665           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8666                     cps->program);
8667         } else {
8668           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8669                     cps->host, appData.remoteUser, cps->program);
8670         }
8671         err = StartChildProcess(buf, "", &cps->pr);
8672     }
8673     
8674     if (err != 0) {
8675         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8676         DisplayFatalError(buf, err, 1);
8677         cps->pr = NoProc;
8678         cps->isr = NULL;
8679         return;
8680     }
8681     
8682     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8683     if (cps->protocolVersion > 1) {
8684       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8685       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8686       cps->comboCnt = 0;  //                and values of combo boxes
8687       SendToProgram(buf, cps);
8688     } else {
8689       SendToProgram("xboard\n", cps);
8690     }
8691 }
8692
8693
8694 void
8695 TwoMachinesEventIfReady P((void))
8696 {
8697   if (first.lastPing != first.lastPong) {
8698     DisplayMessage("", _("Waiting for first chess program"));
8699     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8700     return;
8701   }
8702   if (second.lastPing != second.lastPong) {
8703     DisplayMessage("", _("Waiting for second chess program"));
8704     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8705     return;
8706   }
8707   ThawUI();
8708   TwoMachinesEvent();
8709 }
8710
8711 void
8712 NextMatchGame P((void))
8713 {
8714     int index; /* [HGM] autoinc: step load index during match */
8715     Reset(FALSE, TRUE);
8716     if (*appData.loadGameFile != NULLCHAR) {
8717         index = appData.loadGameIndex;
8718         if(index < 0) { // [HGM] autoinc
8719             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8720             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8721         } 
8722         LoadGameFromFile(appData.loadGameFile,
8723                          index,
8724                          appData.loadGameFile, FALSE);
8725     } else if (*appData.loadPositionFile != NULLCHAR) {
8726         index = appData.loadPositionIndex;
8727         if(index < 0) { // [HGM] autoinc
8728             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8729             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8730         } 
8731         LoadPositionFromFile(appData.loadPositionFile,
8732                              index,
8733                              appData.loadPositionFile);
8734     }
8735     TwoMachinesEventIfReady();
8736 }
8737
8738 void UserAdjudicationEvent( int result )
8739 {
8740     ChessMove gameResult = GameIsDrawn;
8741
8742     if( result > 0 ) {
8743         gameResult = WhiteWins;
8744     }
8745     else if( result < 0 ) {
8746         gameResult = BlackWins;
8747     }
8748
8749     if( gameMode == TwoMachinesPlay ) {
8750         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8751     }
8752 }
8753
8754
8755 // [HGM] save: calculate checksum of game to make games easily identifiable
8756 int StringCheckSum(char *s)
8757 {
8758         int i = 0;
8759         if(s==NULL) return 0;
8760         while(*s) i = i*259 + *s++;
8761         return i;
8762 }
8763
8764 int GameCheckSum()
8765 {
8766         int i, sum=0;
8767         for(i=backwardMostMove; i<forwardMostMove; i++) {
8768                 sum += pvInfoList[i].depth;
8769                 sum += StringCheckSum(parseList[i]);
8770                 sum += StringCheckSum(commentList[i]);
8771                 sum *= 261;
8772         }
8773         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8774         return sum + StringCheckSum(commentList[i]);
8775 } // end of save patch
8776
8777 void
8778 GameEnds(result, resultDetails, whosays)
8779      ChessMove result;
8780      char *resultDetails;
8781      int whosays;
8782 {
8783     GameMode nextGameMode;
8784     int isIcsGame;
8785     char buf[MSG_SIZ];
8786
8787     if(endingGame) return; /* [HGM] crash: forbid recursion */
8788     endingGame = 1;
8789
8790     if (appData.debugMode) {
8791       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8792               result, resultDetails ? resultDetails : "(null)", whosays);
8793     }
8794
8795     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8796
8797     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8798         /* If we are playing on ICS, the server decides when the
8799            game is over, but the engine can offer to draw, claim 
8800            a draw, or resign. 
8801          */
8802 #if ZIPPY
8803         if (appData.zippyPlay && first.initDone) {
8804             if (result == GameIsDrawn) {
8805                 /* In case draw still needs to be claimed */
8806                 SendToICS(ics_prefix);
8807                 SendToICS("draw\n");
8808             } else if (StrCaseStr(resultDetails, "resign")) {
8809                 SendToICS(ics_prefix);
8810                 SendToICS("resign\n");
8811             }
8812         }
8813 #endif
8814         endingGame = 0; /* [HGM] crash */
8815         return;
8816     }
8817
8818     /* If we're loading the game from a file, stop */
8819     if (whosays == GE_FILE) {
8820       (void) StopLoadGameTimer();
8821       gameFileFP = NULL;
8822     }
8823
8824     /* Cancel draw offers */
8825     first.offeredDraw = second.offeredDraw = 0;
8826
8827     /* If this is an ICS game, only ICS can really say it's done;
8828        if not, anyone can. */
8829     isIcsGame = (gameMode == IcsPlayingWhite || 
8830                  gameMode == IcsPlayingBlack || 
8831                  gameMode == IcsObserving    || 
8832                  gameMode == IcsExamining);
8833
8834     if (!isIcsGame || whosays == GE_ICS) {
8835         /* OK -- not an ICS game, or ICS said it was done */
8836         StopClocks();
8837         if (!isIcsGame && !appData.noChessProgram) 
8838           SetUserThinkingEnables();
8839     
8840         /* [HGM] if a machine claims the game end we verify this claim */
8841         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8842             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8843                 char claimer;
8844                 ChessMove trueResult = (ChessMove) -1;
8845
8846                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8847                                             first.twoMachinesColor[0] :
8848                                             second.twoMachinesColor[0] ;
8849
8850                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8851                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8852                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8853                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8854                 } else
8855                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8856                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8857                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8858                 } else
8859                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8860                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8861                 }
8862
8863                 // now verify win claims, but not in drop games, as we don't understand those yet
8864                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8865                                                  || gameInfo.variant == VariantGreat) &&
8866                     (result == WhiteWins && claimer == 'w' ||
8867                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8868                       if (appData.debugMode) {
8869                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8870                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8871                       }
8872                       if(result != trueResult) {
8873                               sprintf(buf, "False win claim: '%s'", resultDetails);
8874                               result = claimer == 'w' ? BlackWins : WhiteWins;
8875                               resultDetails = buf;
8876                       }
8877                 } else
8878                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8879                     && (forwardMostMove <= backwardMostMove ||
8880                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8881                         (claimer=='b')==(forwardMostMove&1))
8882                                                                                   ) {
8883                       /* [HGM] verify: draws that were not flagged are false claims */
8884                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8885                       result = claimer == 'w' ? BlackWins : WhiteWins;
8886                       resultDetails = buf;
8887                 }
8888                 /* (Claiming a loss is accepted no questions asked!) */
8889             }
8890             /* [HGM] bare: don't allow bare King to win */
8891             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8892                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8893                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8894                && result != GameIsDrawn)
8895             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8896                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8897                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8898                         if(p >= 0 && p <= (int)WhiteKing) k++;
8899                 }
8900                 if (appData.debugMode) {
8901                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8902                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8903                 }
8904                 if(k <= 1) {
8905                         result = GameIsDrawn;
8906                         sprintf(buf, "%s but bare king", resultDetails);
8907                         resultDetails = buf;
8908                 }
8909             }
8910         }
8911
8912
8913         if(serverMoves != NULL && !loadFlag) { char c = '=';
8914             if(result==WhiteWins) c = '+';
8915             if(result==BlackWins) c = '-';
8916             if(resultDetails != NULL)
8917                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8918         }
8919         if (resultDetails != NULL) {
8920             gameInfo.result = result;
8921             gameInfo.resultDetails = StrSave(resultDetails);
8922
8923             /* display last move only if game was not loaded from file */
8924             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8925                 DisplayMove(currentMove - 1);
8926     
8927             if (forwardMostMove != 0) {
8928                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8929                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8930                                                                 ) {
8931                     if (*appData.saveGameFile != NULLCHAR) {
8932                         SaveGameToFile(appData.saveGameFile, TRUE);
8933                     } else if (appData.autoSaveGames) {
8934                         AutoSaveGame();
8935                     }
8936                     if (*appData.savePositionFile != NULLCHAR) {
8937                         SavePositionToFile(appData.savePositionFile);
8938                     }
8939                 }
8940             }
8941
8942             /* Tell program how game ended in case it is learning */
8943             /* [HGM] Moved this to after saving the PGN, just in case */
8944             /* engine died and we got here through time loss. In that */
8945             /* case we will get a fatal error writing the pipe, which */
8946             /* would otherwise lose us the PGN.                       */
8947             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8948             /* output during GameEnds should never be fatal anymore   */
8949             if (gameMode == MachinePlaysWhite ||
8950                 gameMode == MachinePlaysBlack ||
8951                 gameMode == TwoMachinesPlay ||
8952                 gameMode == IcsPlayingWhite ||
8953                 gameMode == IcsPlayingBlack ||
8954                 gameMode == BeginningOfGame) {
8955                 char buf[MSG_SIZ];
8956                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8957                         resultDetails);
8958                 if (first.pr != NoProc) {
8959                     SendToProgram(buf, &first);
8960                 }
8961                 if (second.pr != NoProc &&
8962                     gameMode == TwoMachinesPlay) {
8963                     SendToProgram(buf, &second);
8964                 }
8965             }
8966         }
8967
8968         if (appData.icsActive) {
8969             if (appData.quietPlay &&
8970                 (gameMode == IcsPlayingWhite ||
8971                  gameMode == IcsPlayingBlack)) {
8972                 SendToICS(ics_prefix);
8973                 SendToICS("set shout 1\n");
8974             }
8975             nextGameMode = IcsIdle;
8976             ics_user_moved = FALSE;
8977             /* clean up premove.  It's ugly when the game has ended and the
8978              * premove highlights are still on the board.
8979              */
8980             if (gotPremove) {
8981               gotPremove = FALSE;
8982               ClearPremoveHighlights();
8983               DrawPosition(FALSE, boards[currentMove]);
8984             }
8985             if (whosays == GE_ICS) {
8986                 switch (result) {
8987                 case WhiteWins:
8988                     if (gameMode == IcsPlayingWhite)
8989                         PlayIcsWinSound();
8990                     else if(gameMode == IcsPlayingBlack)
8991                         PlayIcsLossSound();
8992                     break;
8993                 case BlackWins:
8994                     if (gameMode == IcsPlayingBlack)
8995                         PlayIcsWinSound();
8996                     else if(gameMode == IcsPlayingWhite)
8997                         PlayIcsLossSound();
8998                     break;
8999                 case GameIsDrawn:
9000                     PlayIcsDrawSound();
9001                     break;
9002                 default:
9003                     PlayIcsUnfinishedSound();
9004                 }
9005             }
9006         } else if (gameMode == EditGame ||
9007                    gameMode == PlayFromGameFile || 
9008                    gameMode == AnalyzeMode || 
9009                    gameMode == AnalyzeFile) {
9010             nextGameMode = gameMode;
9011         } else {
9012             nextGameMode = EndOfGame;
9013         }
9014         pausing = FALSE;
9015         ModeHighlight();
9016     } else {
9017         nextGameMode = gameMode;
9018     }
9019
9020     if (appData.noChessProgram) {
9021         gameMode = nextGameMode;
9022         ModeHighlight();
9023         endingGame = 0; /* [HGM] crash */
9024         return;
9025     }
9026
9027     if (first.reuse) {
9028         /* Put first chess program into idle state */
9029         if (first.pr != NoProc &&
9030             (gameMode == MachinePlaysWhite ||
9031              gameMode == MachinePlaysBlack ||
9032              gameMode == TwoMachinesPlay ||
9033              gameMode == IcsPlayingWhite ||
9034              gameMode == IcsPlayingBlack ||
9035              gameMode == BeginningOfGame)) {
9036             SendToProgram("force\n", &first);
9037             if (first.usePing) {
9038               char buf[MSG_SIZ];
9039               sprintf(buf, "ping %d\n", ++first.lastPing);
9040               SendToProgram(buf, &first);
9041             }
9042         }
9043     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9044         /* Kill off first chess program */
9045         if (first.isr != NULL)
9046           RemoveInputSource(first.isr);
9047         first.isr = NULL;
9048     
9049         if (first.pr != NoProc) {
9050             ExitAnalyzeMode();
9051             DoSleep( appData.delayBeforeQuit );
9052             SendToProgram("quit\n", &first);
9053             DoSleep( appData.delayAfterQuit );
9054             DestroyChildProcess(first.pr, first.useSigterm);
9055         }
9056         first.pr = NoProc;
9057     }
9058     if (second.reuse) {
9059         /* Put second chess program into idle state */
9060         if (second.pr != NoProc &&
9061             gameMode == TwoMachinesPlay) {
9062             SendToProgram("force\n", &second);
9063             if (second.usePing) {
9064               char buf[MSG_SIZ];
9065               sprintf(buf, "ping %d\n", ++second.lastPing);
9066               SendToProgram(buf, &second);
9067             }
9068         }
9069     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9070         /* Kill off second chess program */
9071         if (second.isr != NULL)
9072           RemoveInputSource(second.isr);
9073         second.isr = NULL;
9074     
9075         if (second.pr != NoProc) {
9076             DoSleep( appData.delayBeforeQuit );
9077             SendToProgram("quit\n", &second);
9078             DoSleep( appData.delayAfterQuit );
9079             DestroyChildProcess(second.pr, second.useSigterm);
9080         }
9081         second.pr = NoProc;
9082     }
9083
9084     if (matchMode && gameMode == TwoMachinesPlay) {
9085         switch (result) {
9086         case WhiteWins:
9087           if (first.twoMachinesColor[0] == 'w') {
9088             first.matchWins++;
9089           } else {
9090             second.matchWins++;
9091           }
9092           break;
9093         case BlackWins:
9094           if (first.twoMachinesColor[0] == 'b') {
9095             first.matchWins++;
9096           } else {
9097             second.matchWins++;
9098           }
9099           break;
9100         default:
9101           break;
9102         }
9103         if (matchGame < appData.matchGames) {
9104             char *tmp;
9105             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9106                 tmp = first.twoMachinesColor;
9107                 first.twoMachinesColor = second.twoMachinesColor;
9108                 second.twoMachinesColor = tmp;
9109             }
9110             gameMode = nextGameMode;
9111             matchGame++;
9112             if(appData.matchPause>10000 || appData.matchPause<10)
9113                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9114             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9115             endingGame = 0; /* [HGM] crash */
9116             return;
9117         } else {
9118             char buf[MSG_SIZ];
9119             gameMode = nextGameMode;
9120             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9121                     first.tidy, second.tidy,
9122                     first.matchWins, second.matchWins,
9123                     appData.matchGames - (first.matchWins + second.matchWins));
9124             DisplayFatalError(buf, 0, 0);
9125         }
9126     }
9127     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9128         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9129       ExitAnalyzeMode();
9130     gameMode = nextGameMode;
9131     ModeHighlight();
9132     endingGame = 0;  /* [HGM] crash */
9133 }
9134
9135 /* Assumes program was just initialized (initString sent).
9136    Leaves program in force mode. */
9137 void
9138 FeedMovesToProgram(cps, upto) 
9139      ChessProgramState *cps;
9140      int upto;
9141 {
9142     int i;
9143     
9144     if (appData.debugMode)
9145       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9146               startedFromSetupPosition ? "position and " : "",
9147               backwardMostMove, upto, cps->which);
9148     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9149         // [HGM] variantswitch: make engine aware of new variant
9150         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9151                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9152         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9153         SendToProgram(buf, cps);
9154         currentlyInitializedVariant = gameInfo.variant;
9155     }
9156     SendToProgram("force\n", cps);
9157     if (startedFromSetupPosition) {
9158         SendBoard(cps, backwardMostMove);
9159     if (appData.debugMode) {
9160         fprintf(debugFP, "feedMoves\n");
9161     }
9162     }
9163     for (i = backwardMostMove; i < upto; i++) {
9164         SendMoveToProgram(i, cps);
9165     }
9166 }
9167
9168
9169 void
9170 ResurrectChessProgram()
9171 {
9172      /* The chess program may have exited.
9173         If so, restart it and feed it all the moves made so far. */
9174
9175     if (appData.noChessProgram || first.pr != NoProc) return;
9176     
9177     StartChessProgram(&first);
9178     InitChessProgram(&first, FALSE);
9179     FeedMovesToProgram(&first, currentMove);
9180
9181     if (!first.sendTime) {
9182         /* can't tell gnuchess what its clock should read,
9183            so we bow to its notion. */
9184         ResetClocks();
9185         timeRemaining[0][currentMove] = whiteTimeRemaining;
9186         timeRemaining[1][currentMove] = blackTimeRemaining;
9187     }
9188
9189     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9190                 appData.icsEngineAnalyze) && first.analysisSupport) {
9191       SendToProgram("analyze\n", &first);
9192       first.analyzing = TRUE;
9193     }
9194 }
9195
9196 /*
9197  * Button procedures
9198  */
9199 void
9200 Reset(redraw, init)
9201      int redraw, init;
9202 {
9203     int i;
9204
9205     if (appData.debugMode) {
9206         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9207                 redraw, init, gameMode);
9208     }
9209     CleanupTail(); // [HGM] vari: delete any stored variations
9210     pausing = pauseExamInvalid = FALSE;
9211     startedFromSetupPosition = blackPlaysFirst = FALSE;
9212     firstMove = TRUE;
9213     whiteFlag = blackFlag = FALSE;
9214     userOfferedDraw = FALSE;
9215     hintRequested = bookRequested = FALSE;
9216     first.maybeThinking = FALSE;
9217     second.maybeThinking = FALSE;
9218     first.bookSuspend = FALSE; // [HGM] book
9219     second.bookSuspend = FALSE;
9220     thinkOutput[0] = NULLCHAR;
9221     lastHint[0] = NULLCHAR;
9222     ClearGameInfo(&gameInfo);
9223     gameInfo.variant = StringToVariant(appData.variant);
9224     ics_user_moved = ics_clock_paused = FALSE;
9225     ics_getting_history = H_FALSE;
9226     ics_gamenum = -1;
9227     white_holding[0] = black_holding[0] = NULLCHAR;
9228     ClearProgramStats();
9229     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9230     
9231     ResetFrontEnd();
9232     ClearHighlights();
9233     flipView = appData.flipView;
9234     ClearPremoveHighlights();
9235     gotPremove = FALSE;
9236     alarmSounded = FALSE;
9237
9238     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9239     if(appData.serverMovesName != NULL) {
9240         /* [HGM] prepare to make moves file for broadcasting */
9241         clock_t t = clock();
9242         if(serverMoves != NULL) fclose(serverMoves);
9243         serverMoves = fopen(appData.serverMovesName, "r");
9244         if(serverMoves != NULL) {
9245             fclose(serverMoves);
9246             /* delay 15 sec before overwriting, so all clients can see end */
9247             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9248         }
9249         serverMoves = fopen(appData.serverMovesName, "w");
9250     }
9251
9252     ExitAnalyzeMode();
9253     gameMode = BeginningOfGame;
9254     ModeHighlight();
9255     if(appData.icsActive) gameInfo.variant = VariantNormal;
9256     currentMove = forwardMostMove = backwardMostMove = 0;
9257     InitPosition(redraw);
9258     for (i = 0; i < MAX_MOVES; i++) {
9259         if (commentList[i] != NULL) {
9260             free(commentList[i]);
9261             commentList[i] = NULL;
9262         }
9263     }
9264     ResetClocks();
9265     timeRemaining[0][0] = whiteTimeRemaining;
9266     timeRemaining[1][0] = blackTimeRemaining;
9267     if (first.pr == NULL) {
9268         StartChessProgram(&first);
9269     }
9270     if (init) {
9271             InitChessProgram(&first, startedFromSetupPosition);
9272     }
9273     DisplayTitle("");
9274     DisplayMessage("", "");
9275     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9276     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9277 }
9278
9279 void
9280 AutoPlayGameLoop()
9281 {
9282     for (;;) {
9283         if (!AutoPlayOneMove())
9284           return;
9285         if (matchMode || appData.timeDelay == 0)
9286           continue;
9287         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9288           return;
9289         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9290         break;
9291     }
9292 }
9293
9294
9295 int
9296 AutoPlayOneMove()
9297 {
9298     int fromX, fromY, toX, toY;
9299
9300     if (appData.debugMode) {
9301       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9302     }
9303
9304     if (gameMode != PlayFromGameFile)
9305       return FALSE;
9306
9307     if (currentMove >= forwardMostMove) {
9308       gameMode = EditGame;
9309       ModeHighlight();
9310
9311       /* [AS] Clear current move marker at the end of a game */
9312       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9313
9314       return FALSE;
9315     }
9316     
9317     toX = moveList[currentMove][2] - AAA;
9318     toY = moveList[currentMove][3] - ONE;
9319
9320     if (moveList[currentMove][1] == '@') {
9321         if (appData.highlightLastMove) {
9322             SetHighlights(-1, -1, toX, toY);
9323         }
9324     } else {
9325         fromX = moveList[currentMove][0] - AAA;
9326         fromY = moveList[currentMove][1] - ONE;
9327
9328         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9329
9330         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9331
9332         if (appData.highlightLastMove) {
9333             SetHighlights(fromX, fromY, toX, toY);
9334         }
9335     }
9336     DisplayMove(currentMove);
9337     SendMoveToProgram(currentMove++, &first);
9338     DisplayBothClocks();
9339     DrawPosition(FALSE, boards[currentMove]);
9340     // [HGM] PV info: always display, routine tests if empty
9341     DisplayComment(currentMove - 1, commentList[currentMove]);
9342     return TRUE;
9343 }
9344
9345
9346 int
9347 LoadGameOneMove(readAhead)
9348      ChessMove readAhead;
9349 {
9350     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9351     char promoChar = NULLCHAR;
9352     ChessMove moveType;
9353     char move[MSG_SIZ];
9354     char *p, *q;
9355     
9356     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9357         gameMode != AnalyzeMode && gameMode != Training) {
9358         gameFileFP = NULL;
9359         return FALSE;
9360     }
9361     
9362     yyboardindex = forwardMostMove;
9363     if (readAhead != (ChessMove)0) {
9364       moveType = readAhead;
9365     } else {
9366       if (gameFileFP == NULL)
9367           return FALSE;
9368       moveType = (ChessMove) yylex();
9369     }
9370     
9371     done = FALSE;
9372     switch (moveType) {
9373       case Comment:
9374         if (appData.debugMode) 
9375           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9376         p = yy_text;
9377
9378         /* append the comment but don't display it */
9379         AppendComment(currentMove, p, FALSE);
9380         return TRUE;
9381
9382       case WhiteCapturesEnPassant:
9383       case BlackCapturesEnPassant:
9384       case WhitePromotionChancellor:
9385       case BlackPromotionChancellor:
9386       case WhitePromotionArchbishop:
9387       case BlackPromotionArchbishop:
9388       case WhitePromotionCentaur:
9389       case BlackPromotionCentaur:
9390       case WhitePromotionQueen:
9391       case BlackPromotionQueen:
9392       case WhitePromotionRook:
9393       case BlackPromotionRook:
9394       case WhitePromotionBishop:
9395       case BlackPromotionBishop:
9396       case WhitePromotionKnight:
9397       case BlackPromotionKnight:
9398       case WhitePromotionKing:
9399       case BlackPromotionKing:
9400       case NormalMove:
9401       case WhiteKingSideCastle:
9402       case WhiteQueenSideCastle:
9403       case BlackKingSideCastle:
9404       case BlackQueenSideCastle:
9405       case WhiteKingSideCastleWild:
9406       case WhiteQueenSideCastleWild:
9407       case BlackKingSideCastleWild:
9408       case BlackQueenSideCastleWild:
9409       /* PUSH Fabien */
9410       case WhiteHSideCastleFR:
9411       case WhiteASideCastleFR:
9412       case BlackHSideCastleFR:
9413       case BlackASideCastleFR:
9414       /* POP Fabien */
9415         if (appData.debugMode)
9416           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9417         fromX = currentMoveString[0] - AAA;
9418         fromY = currentMoveString[1] - ONE;
9419         toX = currentMoveString[2] - AAA;
9420         toY = currentMoveString[3] - ONE;
9421         promoChar = currentMoveString[4];
9422         break;
9423
9424       case WhiteDrop:
9425       case BlackDrop:
9426         if (appData.debugMode)
9427           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9428         fromX = moveType == WhiteDrop ?
9429           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9430         (int) CharToPiece(ToLower(currentMoveString[0]));
9431         fromY = DROP_RANK;
9432         toX = currentMoveString[2] - AAA;
9433         toY = currentMoveString[3] - ONE;
9434         break;
9435
9436       case WhiteWins:
9437       case BlackWins:
9438       case GameIsDrawn:
9439       case GameUnfinished:
9440         if (appData.debugMode)
9441           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9442         p = strchr(yy_text, '{');
9443         if (p == NULL) p = strchr(yy_text, '(');
9444         if (p == NULL) {
9445             p = yy_text;
9446             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9447         } else {
9448             q = strchr(p, *p == '{' ? '}' : ')');
9449             if (q != NULL) *q = NULLCHAR;
9450             p++;
9451         }
9452         GameEnds(moveType, p, GE_FILE);
9453         done = TRUE;
9454         if (cmailMsgLoaded) {
9455             ClearHighlights();
9456             flipView = WhiteOnMove(currentMove);
9457             if (moveType == GameUnfinished) flipView = !flipView;
9458             if (appData.debugMode)
9459               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9460         }
9461         break;
9462
9463       case (ChessMove) 0:       /* end of file */
9464         if (appData.debugMode)
9465           fprintf(debugFP, "Parser hit end of file\n");
9466         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9467           case MT_NONE:
9468           case MT_CHECK:
9469             break;
9470           case MT_CHECKMATE:
9471           case MT_STAINMATE:
9472             if (WhiteOnMove(currentMove)) {
9473                 GameEnds(BlackWins, "Black mates", GE_FILE);
9474             } else {
9475                 GameEnds(WhiteWins, "White mates", GE_FILE);
9476             }
9477             break;
9478           case MT_STALEMATE:
9479             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9480             break;
9481         }
9482         done = TRUE;
9483         break;
9484
9485       case MoveNumberOne:
9486         if (lastLoadGameStart == GNUChessGame) {
9487             /* GNUChessGames have numbers, but they aren't move numbers */
9488             if (appData.debugMode)
9489               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9490                       yy_text, (int) moveType);
9491             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9492         }
9493         /* else fall thru */
9494
9495       case XBoardGame:
9496       case GNUChessGame:
9497       case PGNTag:
9498         /* Reached start of next game in file */
9499         if (appData.debugMode)
9500           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9501         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9502           case MT_NONE:
9503           case MT_CHECK:
9504             break;
9505           case MT_CHECKMATE:
9506           case MT_STAINMATE:
9507             if (WhiteOnMove(currentMove)) {
9508                 GameEnds(BlackWins, "Black mates", GE_FILE);
9509             } else {
9510                 GameEnds(WhiteWins, "White mates", GE_FILE);
9511             }
9512             break;
9513           case MT_STALEMATE:
9514             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9515             break;
9516         }
9517         done = TRUE;
9518         break;
9519
9520       case PositionDiagram:     /* should not happen; ignore */
9521       case ElapsedTime:         /* ignore */
9522       case NAG:                 /* ignore */
9523         if (appData.debugMode)
9524           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9525                   yy_text, (int) moveType);
9526         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9527
9528       case IllegalMove:
9529         if (appData.testLegality) {
9530             if (appData.debugMode)
9531               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9532             sprintf(move, _("Illegal move: %d.%s%s"),
9533                     (forwardMostMove / 2) + 1,
9534                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9535             DisplayError(move, 0);
9536             done = TRUE;
9537         } else {
9538             if (appData.debugMode)
9539               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9540                       yy_text, currentMoveString);
9541             fromX = currentMoveString[0] - AAA;
9542             fromY = currentMoveString[1] - ONE;
9543             toX = currentMoveString[2] - AAA;
9544             toY = currentMoveString[3] - ONE;
9545             promoChar = currentMoveString[4];
9546         }
9547         break;
9548
9549       case AmbiguousMove:
9550         if (appData.debugMode)
9551           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9552         sprintf(move, _("Ambiguous move: %d.%s%s"),
9553                 (forwardMostMove / 2) + 1,
9554                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9555         DisplayError(move, 0);
9556         done = TRUE;
9557         break;
9558
9559       default:
9560       case ImpossibleMove:
9561         if (appData.debugMode)
9562           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9563         sprintf(move, _("Illegal move: %d.%s%s"),
9564                 (forwardMostMove / 2) + 1,
9565                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9566         DisplayError(move, 0);
9567         done = TRUE;
9568         break;
9569     }
9570
9571     if (done) {
9572         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9573             DrawPosition(FALSE, boards[currentMove]);
9574             DisplayBothClocks();
9575             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9576               DisplayComment(currentMove - 1, commentList[currentMove]);
9577         }
9578         (void) StopLoadGameTimer();
9579         gameFileFP = NULL;
9580         cmailOldMove = forwardMostMove;
9581         return FALSE;
9582     } else {
9583         /* currentMoveString is set as a side-effect of yylex */
9584         strcat(currentMoveString, "\n");
9585         strcpy(moveList[forwardMostMove], currentMoveString);
9586         
9587         thinkOutput[0] = NULLCHAR;
9588         MakeMove(fromX, fromY, toX, toY, promoChar);
9589         currentMove = forwardMostMove;
9590         return TRUE;
9591     }
9592 }
9593
9594 /* Load the nth game from the given file */
9595 int
9596 LoadGameFromFile(filename, n, title, useList)
9597      char *filename;
9598      int n;
9599      char *title;
9600      /*Boolean*/ int useList;
9601 {
9602     FILE *f;
9603     char buf[MSG_SIZ];
9604
9605     if (strcmp(filename, "-") == 0) {
9606         f = stdin;
9607         title = "stdin";
9608     } else {
9609         f = fopen(filename, "rb");
9610         if (f == NULL) {
9611           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9612             DisplayError(buf, errno);
9613             return FALSE;
9614         }
9615     }
9616     if (fseek(f, 0, 0) == -1) {
9617         /* f is not seekable; probably a pipe */
9618         useList = FALSE;
9619     }
9620     if (useList && n == 0) {
9621         int error = GameListBuild(f);
9622         if (error) {
9623             DisplayError(_("Cannot build game list"), error);
9624         } else if (!ListEmpty(&gameList) &&
9625                    ((ListGame *) gameList.tailPred)->number > 1) {
9626             GameListPopUp(f, title);
9627             return TRUE;
9628         }
9629         GameListDestroy();
9630         n = 1;
9631     }
9632     if (n == 0) n = 1;
9633     return LoadGame(f, n, title, FALSE);
9634 }
9635
9636
9637 void
9638 MakeRegisteredMove()
9639 {
9640     int fromX, fromY, toX, toY;
9641     char promoChar;
9642     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9643         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9644           case CMAIL_MOVE:
9645           case CMAIL_DRAW:
9646             if (appData.debugMode)
9647               fprintf(debugFP, "Restoring %s for game %d\n",
9648                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9649     
9650             thinkOutput[0] = NULLCHAR;
9651             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9652             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9653             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9654             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9655             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9656             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9657             MakeMove(fromX, fromY, toX, toY, promoChar);
9658             ShowMove(fromX, fromY, toX, toY);
9659               
9660             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9661               case MT_NONE:
9662               case MT_CHECK:
9663                 break;
9664                 
9665               case MT_CHECKMATE:
9666               case MT_STAINMATE:
9667                 if (WhiteOnMove(currentMove)) {
9668                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9669                 } else {
9670                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9671                 }
9672                 break;
9673                 
9674               case MT_STALEMATE:
9675                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9676                 break;
9677             }
9678
9679             break;
9680             
9681           case CMAIL_RESIGN:
9682             if (WhiteOnMove(currentMove)) {
9683                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9684             } else {
9685                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9686             }
9687             break;
9688             
9689           case CMAIL_ACCEPT:
9690             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9691             break;
9692               
9693           default:
9694             break;
9695         }
9696     }
9697
9698     return;
9699 }
9700
9701 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9702 int
9703 CmailLoadGame(f, gameNumber, title, useList)
9704      FILE *f;
9705      int gameNumber;
9706      char *title;
9707      int useList;
9708 {
9709     int retVal;
9710
9711     if (gameNumber > nCmailGames) {
9712         DisplayError(_("No more games in this message"), 0);
9713         return FALSE;
9714     }
9715     if (f == lastLoadGameFP) {
9716         int offset = gameNumber - lastLoadGameNumber;
9717         if (offset == 0) {
9718             cmailMsg[0] = NULLCHAR;
9719             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9720                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9721                 nCmailMovesRegistered--;
9722             }
9723             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9724             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9725                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9726             }
9727         } else {
9728             if (! RegisterMove()) return FALSE;
9729         }
9730     }
9731
9732     retVal = LoadGame(f, gameNumber, title, useList);
9733
9734     /* Make move registered during previous look at this game, if any */
9735     MakeRegisteredMove();
9736
9737     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9738         commentList[currentMove]
9739           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9740         DisplayComment(currentMove - 1, commentList[currentMove]);
9741     }
9742
9743     return retVal;
9744 }
9745
9746 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9747 int
9748 ReloadGame(offset)
9749      int offset;
9750 {
9751     int gameNumber = lastLoadGameNumber + offset;
9752     if (lastLoadGameFP == NULL) {
9753         DisplayError(_("No game has been loaded yet"), 0);
9754         return FALSE;
9755     }
9756     if (gameNumber <= 0) {
9757         DisplayError(_("Can't back up any further"), 0);
9758         return FALSE;
9759     }
9760     if (cmailMsgLoaded) {
9761         return CmailLoadGame(lastLoadGameFP, gameNumber,
9762                              lastLoadGameTitle, lastLoadGameUseList);
9763     } else {
9764         return LoadGame(lastLoadGameFP, gameNumber,
9765                         lastLoadGameTitle, lastLoadGameUseList);
9766     }
9767 }
9768
9769
9770
9771 /* Load the nth game from open file f */
9772 int
9773 LoadGame(f, gameNumber, title, useList)
9774      FILE *f;
9775      int gameNumber;
9776      char *title;
9777      int useList;
9778 {
9779     ChessMove cm;
9780     char buf[MSG_SIZ];
9781     int gn = gameNumber;
9782     ListGame *lg = NULL;
9783     int numPGNTags = 0;
9784     int err;
9785     GameMode oldGameMode;
9786     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9787
9788     if (appData.debugMode) 
9789         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9790
9791     if (gameMode == Training )
9792         SetTrainingModeOff();
9793
9794     oldGameMode = gameMode;
9795     if (gameMode != BeginningOfGame) {
9796       Reset(FALSE, TRUE);
9797     }
9798
9799     gameFileFP = f;
9800     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9801         fclose(lastLoadGameFP);
9802     }
9803
9804     if (useList) {
9805         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9806         
9807         if (lg) {
9808             fseek(f, lg->offset, 0);
9809             GameListHighlight(gameNumber);
9810             gn = 1;
9811         }
9812         else {
9813             DisplayError(_("Game number out of range"), 0);
9814             return FALSE;
9815         }
9816     } else {
9817         GameListDestroy();
9818         if (fseek(f, 0, 0) == -1) {
9819             if (f == lastLoadGameFP ?
9820                 gameNumber == lastLoadGameNumber + 1 :
9821                 gameNumber == 1) {
9822                 gn = 1;
9823             } else {
9824                 DisplayError(_("Can't seek on game file"), 0);
9825                 return FALSE;
9826             }
9827         }
9828     }
9829     lastLoadGameFP = f;
9830     lastLoadGameNumber = gameNumber;
9831     strcpy(lastLoadGameTitle, title);
9832     lastLoadGameUseList = useList;
9833
9834     yynewfile(f);
9835
9836     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9837       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9838                 lg->gameInfo.black);
9839             DisplayTitle(buf);
9840     } else if (*title != NULLCHAR) {
9841         if (gameNumber > 1) {
9842             sprintf(buf, "%s %d", title, gameNumber);
9843             DisplayTitle(buf);
9844         } else {
9845             DisplayTitle(title);
9846         }
9847     }
9848
9849     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9850         gameMode = PlayFromGameFile;
9851         ModeHighlight();
9852     }
9853
9854     currentMove = forwardMostMove = backwardMostMove = 0;
9855     CopyBoard(boards[0], initialPosition);
9856     StopClocks();
9857
9858     /*
9859      * Skip the first gn-1 games in the file.
9860      * Also skip over anything that precedes an identifiable 
9861      * start of game marker, to avoid being confused by 
9862      * garbage at the start of the file.  Currently 
9863      * recognized start of game markers are the move number "1",
9864      * the pattern "gnuchess .* game", the pattern
9865      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9866      * A game that starts with one of the latter two patterns
9867      * will also have a move number 1, possibly
9868      * following a position diagram.
9869      * 5-4-02: Let's try being more lenient and allowing a game to
9870      * start with an unnumbered move.  Does that break anything?
9871      */
9872     cm = lastLoadGameStart = (ChessMove) 0;
9873     while (gn > 0) {
9874         yyboardindex = forwardMostMove;
9875         cm = (ChessMove) yylex();
9876         switch (cm) {
9877           case (ChessMove) 0:
9878             if (cmailMsgLoaded) {
9879                 nCmailGames = CMAIL_MAX_GAMES - gn;
9880             } else {
9881                 Reset(TRUE, TRUE);
9882                 DisplayError(_("Game not found in file"), 0);
9883             }
9884             return FALSE;
9885
9886           case GNUChessGame:
9887           case XBoardGame:
9888             gn--;
9889             lastLoadGameStart = cm;
9890             break;
9891             
9892           case MoveNumberOne:
9893             switch (lastLoadGameStart) {
9894               case GNUChessGame:
9895               case XBoardGame:
9896               case PGNTag:
9897                 break;
9898               case MoveNumberOne:
9899               case (ChessMove) 0:
9900                 gn--;           /* count this game */
9901                 lastLoadGameStart = cm;
9902                 break;
9903               default:
9904                 /* impossible */
9905                 break;
9906             }
9907             break;
9908
9909           case PGNTag:
9910             switch (lastLoadGameStart) {
9911               case GNUChessGame:
9912               case PGNTag:
9913               case MoveNumberOne:
9914               case (ChessMove) 0:
9915                 gn--;           /* count this game */
9916                 lastLoadGameStart = cm;
9917                 break;
9918               case XBoardGame:
9919                 lastLoadGameStart = cm; /* game counted already */
9920                 break;
9921               default:
9922                 /* impossible */
9923                 break;
9924             }
9925             if (gn > 0) {
9926                 do {
9927                     yyboardindex = forwardMostMove;
9928                     cm = (ChessMove) yylex();
9929                 } while (cm == PGNTag || cm == Comment);
9930             }
9931             break;
9932
9933           case WhiteWins:
9934           case BlackWins:
9935           case GameIsDrawn:
9936             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9937                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9938                     != CMAIL_OLD_RESULT) {
9939                     nCmailResults ++ ;
9940                     cmailResult[  CMAIL_MAX_GAMES
9941                                 - gn - 1] = CMAIL_OLD_RESULT;
9942                 }
9943             }
9944             break;
9945
9946           case NormalMove:
9947             /* Only a NormalMove can be at the start of a game
9948              * without a position diagram. */
9949             if (lastLoadGameStart == (ChessMove) 0) {
9950               gn--;
9951               lastLoadGameStart = MoveNumberOne;
9952             }
9953             break;
9954
9955           default:
9956             break;
9957         }
9958     }
9959     
9960     if (appData.debugMode)
9961       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9962
9963     if (cm == XBoardGame) {
9964         /* Skip any header junk before position diagram and/or move 1 */
9965         for (;;) {
9966             yyboardindex = forwardMostMove;
9967             cm = (ChessMove) yylex();
9968
9969             if (cm == (ChessMove) 0 ||
9970                 cm == GNUChessGame || cm == XBoardGame) {
9971                 /* Empty game; pretend end-of-file and handle later */
9972                 cm = (ChessMove) 0;
9973                 break;
9974             }
9975
9976             if (cm == MoveNumberOne || cm == PositionDiagram ||
9977                 cm == PGNTag || cm == Comment)
9978               break;
9979         }
9980     } else if (cm == GNUChessGame) {
9981         if (gameInfo.event != NULL) {
9982             free(gameInfo.event);
9983         }
9984         gameInfo.event = StrSave(yy_text);
9985     }   
9986
9987     startedFromSetupPosition = FALSE;
9988     while (cm == PGNTag) {
9989         if (appData.debugMode) 
9990           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9991         err = ParsePGNTag(yy_text, &gameInfo);
9992         if (!err) numPGNTags++;
9993
9994         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9995         if(gameInfo.variant != oldVariant) {
9996             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9997             InitPosition(TRUE);
9998             oldVariant = gameInfo.variant;
9999             if (appData.debugMode) 
10000               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10001         }
10002
10003
10004         if (gameInfo.fen != NULL) {
10005           Board initial_position;
10006           startedFromSetupPosition = TRUE;
10007           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10008             Reset(TRUE, TRUE);
10009             DisplayError(_("Bad FEN position in file"), 0);
10010             return FALSE;
10011           }
10012           CopyBoard(boards[0], initial_position);
10013           if (blackPlaysFirst) {
10014             currentMove = forwardMostMove = backwardMostMove = 1;
10015             CopyBoard(boards[1], initial_position);
10016             strcpy(moveList[0], "");
10017             strcpy(parseList[0], "");
10018             timeRemaining[0][1] = whiteTimeRemaining;
10019             timeRemaining[1][1] = blackTimeRemaining;
10020             if (commentList[0] != NULL) {
10021               commentList[1] = commentList[0];
10022               commentList[0] = NULL;
10023             }
10024           } else {
10025             currentMove = forwardMostMove = backwardMostMove = 0;
10026           }
10027           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10028           {   int i;
10029               initialRulePlies = FENrulePlies;
10030               for( i=0; i< nrCastlingRights; i++ )
10031                   initialRights[i] = initial_position[CASTLING][i];
10032           }
10033           yyboardindex = forwardMostMove;
10034           free(gameInfo.fen);
10035           gameInfo.fen = NULL;
10036         }
10037
10038         yyboardindex = forwardMostMove;
10039         cm = (ChessMove) yylex();
10040
10041         /* Handle comments interspersed among the tags */
10042         while (cm == Comment) {
10043             char *p;
10044             if (appData.debugMode) 
10045               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10046             p = yy_text;
10047             AppendComment(currentMove, p, FALSE);
10048             yyboardindex = forwardMostMove;
10049             cm = (ChessMove) yylex();
10050         }
10051     }
10052
10053     /* don't rely on existence of Event tag since if game was
10054      * pasted from clipboard the Event tag may not exist
10055      */
10056     if (numPGNTags > 0){
10057         char *tags;
10058         if (gameInfo.variant == VariantNormal) {
10059           gameInfo.variant = StringToVariant(gameInfo.event);
10060         }
10061         if (!matchMode) {
10062           if( appData.autoDisplayTags ) {
10063             tags = PGNTags(&gameInfo);
10064             TagsPopUp(tags, CmailMsg());
10065             free(tags);
10066           }
10067         }
10068     } else {
10069         /* Make something up, but don't display it now */
10070         SetGameInfo();
10071         TagsPopDown();
10072     }
10073
10074     if (cm == PositionDiagram) {
10075         int i, j;
10076         char *p;
10077         Board initial_position;
10078
10079         if (appData.debugMode)
10080           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10081
10082         if (!startedFromSetupPosition) {
10083             p = yy_text;
10084             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10085               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10086                 switch (*p) {
10087                   case '[':
10088                   case '-':
10089                   case ' ':
10090                   case '\t':
10091                   case '\n':
10092                   case '\r':
10093                     break;
10094                   default:
10095                     initial_position[i][j++] = CharToPiece(*p);
10096                     break;
10097                 }
10098             while (*p == ' ' || *p == '\t' ||
10099                    *p == '\n' || *p == '\r') p++;
10100         
10101             if (strncmp(p, "black", strlen("black"))==0)
10102               blackPlaysFirst = TRUE;
10103             else
10104               blackPlaysFirst = FALSE;
10105             startedFromSetupPosition = TRUE;
10106         
10107             CopyBoard(boards[0], initial_position);
10108             if (blackPlaysFirst) {
10109                 currentMove = forwardMostMove = backwardMostMove = 1;
10110                 CopyBoard(boards[1], initial_position);
10111                 strcpy(moveList[0], "");
10112                 strcpy(parseList[0], "");
10113                 timeRemaining[0][1] = whiteTimeRemaining;
10114                 timeRemaining[1][1] = blackTimeRemaining;
10115                 if (commentList[0] != NULL) {
10116                     commentList[1] = commentList[0];
10117                     commentList[0] = NULL;
10118                 }
10119             } else {
10120                 currentMove = forwardMostMove = backwardMostMove = 0;
10121             }
10122         }
10123         yyboardindex = forwardMostMove;
10124         cm = (ChessMove) yylex();
10125     }
10126
10127     if (first.pr == NoProc) {
10128         StartChessProgram(&first);
10129     }
10130     InitChessProgram(&first, FALSE);
10131     SendToProgram("force\n", &first);
10132     if (startedFromSetupPosition) {
10133         SendBoard(&first, forwardMostMove);
10134     if (appData.debugMode) {
10135         fprintf(debugFP, "Load Game\n");
10136     }
10137         DisplayBothClocks();
10138     }      
10139
10140     /* [HGM] server: flag to write setup moves in broadcast file as one */
10141     loadFlag = appData.suppressLoadMoves;
10142
10143     while (cm == Comment) {
10144         char *p;
10145         if (appData.debugMode) 
10146           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10147         p = yy_text;
10148         AppendComment(currentMove, p, FALSE);
10149         yyboardindex = forwardMostMove;
10150         cm = (ChessMove) yylex();
10151     }
10152
10153     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10154         cm == WhiteWins || cm == BlackWins ||
10155         cm == GameIsDrawn || cm == GameUnfinished) {
10156         DisplayMessage("", _("No moves in game"));
10157         if (cmailMsgLoaded) {
10158             if (appData.debugMode)
10159               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10160             ClearHighlights();
10161             flipView = FALSE;
10162         }
10163         DrawPosition(FALSE, boards[currentMove]);
10164         DisplayBothClocks();
10165         gameMode = EditGame;
10166         ModeHighlight();
10167         gameFileFP = NULL;
10168         cmailOldMove = 0;
10169         return TRUE;
10170     }
10171
10172     // [HGM] PV info: routine tests if comment empty
10173     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10174         DisplayComment(currentMove - 1, commentList[currentMove]);
10175     }
10176     if (!matchMode && appData.timeDelay != 0) 
10177       DrawPosition(FALSE, boards[currentMove]);
10178
10179     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10180       programStats.ok_to_send = 1;
10181     }
10182
10183     /* if the first token after the PGN tags is a move
10184      * and not move number 1, retrieve it from the parser 
10185      */
10186     if (cm != MoveNumberOne)
10187         LoadGameOneMove(cm);
10188
10189     /* load the remaining moves from the file */
10190     while (LoadGameOneMove((ChessMove)0)) {
10191       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10192       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10193     }
10194
10195     /* rewind to the start of the game */
10196     currentMove = backwardMostMove;
10197
10198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10199
10200     if (oldGameMode == AnalyzeFile ||
10201         oldGameMode == AnalyzeMode) {
10202       AnalyzeFileEvent();
10203     }
10204
10205     if (matchMode || appData.timeDelay == 0) {
10206       ToEndEvent();
10207       gameMode = EditGame;
10208       ModeHighlight();
10209     } else if (appData.timeDelay > 0) {
10210       AutoPlayGameLoop();
10211     }
10212
10213     if (appData.debugMode) 
10214         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10215
10216     loadFlag = 0; /* [HGM] true game starts */
10217     return TRUE;
10218 }
10219
10220 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10221 int
10222 ReloadPosition(offset)
10223      int offset;
10224 {
10225     int positionNumber = lastLoadPositionNumber + offset;
10226     if (lastLoadPositionFP == NULL) {
10227         DisplayError(_("No position has been loaded yet"), 0);
10228         return FALSE;
10229     }
10230     if (positionNumber <= 0) {
10231         DisplayError(_("Can't back up any further"), 0);
10232         return FALSE;
10233     }
10234     return LoadPosition(lastLoadPositionFP, positionNumber,
10235                         lastLoadPositionTitle);
10236 }
10237
10238 /* Load the nth position from the given file */
10239 int
10240 LoadPositionFromFile(filename, n, title)
10241      char *filename;
10242      int n;
10243      char *title;
10244 {
10245     FILE *f;
10246     char buf[MSG_SIZ];
10247
10248     if (strcmp(filename, "-") == 0) {
10249         return LoadPosition(stdin, n, "stdin");
10250     } else {
10251         f = fopen(filename, "rb");
10252         if (f == NULL) {
10253             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10254             DisplayError(buf, errno);
10255             return FALSE;
10256         } else {
10257             return LoadPosition(f, n, title);
10258         }
10259     }
10260 }
10261
10262 /* Load the nth position from the given open file, and close it */
10263 int
10264 LoadPosition(f, positionNumber, title)
10265      FILE *f;
10266      int positionNumber;
10267      char *title;
10268 {
10269     char *p, line[MSG_SIZ];
10270     Board initial_position;
10271     int i, j, fenMode, pn;
10272     
10273     if (gameMode == Training )
10274         SetTrainingModeOff();
10275
10276     if (gameMode != BeginningOfGame) {
10277         Reset(FALSE, TRUE);
10278     }
10279     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10280         fclose(lastLoadPositionFP);
10281     }
10282     if (positionNumber == 0) positionNumber = 1;
10283     lastLoadPositionFP = f;
10284     lastLoadPositionNumber = positionNumber;
10285     strcpy(lastLoadPositionTitle, title);
10286     if (first.pr == NoProc) {
10287       StartChessProgram(&first);
10288       InitChessProgram(&first, FALSE);
10289     }    
10290     pn = positionNumber;
10291     if (positionNumber < 0) {
10292         /* Negative position number means to seek to that byte offset */
10293         if (fseek(f, -positionNumber, 0) == -1) {
10294             DisplayError(_("Can't seek on position file"), 0);
10295             return FALSE;
10296         };
10297         pn = 1;
10298     } else {
10299         if (fseek(f, 0, 0) == -1) {
10300             if (f == lastLoadPositionFP ?
10301                 positionNumber == lastLoadPositionNumber + 1 :
10302                 positionNumber == 1) {
10303                 pn = 1;
10304             } else {
10305                 DisplayError(_("Can't seek on position file"), 0);
10306                 return FALSE;
10307             }
10308         }
10309     }
10310     /* See if this file is FEN or old-style xboard */
10311     if (fgets(line, MSG_SIZ, f) == NULL) {
10312         DisplayError(_("Position not found in file"), 0);
10313         return FALSE;
10314     }
10315     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10316     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10317
10318     if (pn >= 2) {
10319         if (fenMode || line[0] == '#') pn--;
10320         while (pn > 0) {
10321             /* skip positions before number pn */
10322             if (fgets(line, MSG_SIZ, f) == NULL) {
10323                 Reset(TRUE, TRUE);
10324                 DisplayError(_("Position not found in file"), 0);
10325                 return FALSE;
10326             }
10327             if (fenMode || line[0] == '#') pn--;
10328         }
10329     }
10330
10331     if (fenMode) {
10332         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10333             DisplayError(_("Bad FEN position in file"), 0);
10334             return FALSE;
10335         }
10336     } else {
10337         (void) fgets(line, MSG_SIZ, f);
10338         (void) fgets(line, MSG_SIZ, f);
10339     
10340         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10341             (void) fgets(line, MSG_SIZ, f);
10342             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10343                 if (*p == ' ')
10344                   continue;
10345                 initial_position[i][j++] = CharToPiece(*p);
10346             }
10347         }
10348     
10349         blackPlaysFirst = FALSE;
10350         if (!feof(f)) {
10351             (void) fgets(line, MSG_SIZ, f);
10352             if (strncmp(line, "black", strlen("black"))==0)
10353               blackPlaysFirst = TRUE;
10354         }
10355     }
10356     startedFromSetupPosition = TRUE;
10357     
10358     SendToProgram("force\n", &first);
10359     CopyBoard(boards[0], initial_position);
10360     if (blackPlaysFirst) {
10361         currentMove = forwardMostMove = backwardMostMove = 1;
10362         strcpy(moveList[0], "");
10363         strcpy(parseList[0], "");
10364         CopyBoard(boards[1], initial_position);
10365         DisplayMessage("", _("Black to play"));
10366     } else {
10367         currentMove = forwardMostMove = backwardMostMove = 0;
10368         DisplayMessage("", _("White to play"));
10369     }
10370     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10371     SendBoard(&first, forwardMostMove);
10372     if (appData.debugMode) {
10373 int i, j;
10374   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10375   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10376         fprintf(debugFP, "Load Position\n");
10377     }
10378
10379     if (positionNumber > 1) {
10380         sprintf(line, "%s %d", title, positionNumber);
10381         DisplayTitle(line);
10382     } else {
10383         DisplayTitle(title);
10384     }
10385     gameMode = EditGame;
10386     ModeHighlight();
10387     ResetClocks();
10388     timeRemaining[0][1] = whiteTimeRemaining;
10389     timeRemaining[1][1] = blackTimeRemaining;
10390     DrawPosition(FALSE, boards[currentMove]);
10391    
10392     return TRUE;
10393 }
10394
10395
10396 void
10397 CopyPlayerNameIntoFileName(dest, src)
10398      char **dest, *src;
10399 {
10400     while (*src != NULLCHAR && *src != ',') {
10401         if (*src == ' ') {
10402             *(*dest)++ = '_';
10403             src++;
10404         } else {
10405             *(*dest)++ = *src++;
10406         }
10407     }
10408 }
10409
10410 char *DefaultFileName(ext)
10411      char *ext;
10412 {
10413     static char def[MSG_SIZ];
10414     char *p;
10415
10416     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10417         p = def;
10418         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10419         *p++ = '-';
10420         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10421         *p++ = '.';
10422         strcpy(p, ext);
10423     } else {
10424         def[0] = NULLCHAR;
10425     }
10426     return def;
10427 }
10428
10429 /* Save the current game to the given file */
10430 int
10431 SaveGameToFile(filename, append)
10432      char *filename;
10433      int append;
10434 {
10435     FILE *f;
10436     char buf[MSG_SIZ];
10437
10438     if (strcmp(filename, "-") == 0) {
10439         return SaveGame(stdout, 0, NULL);
10440     } else {
10441         f = fopen(filename, append ? "a" : "w");
10442         if (f == NULL) {
10443             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10444             DisplayError(buf, errno);
10445             return FALSE;
10446         } else {
10447             return SaveGame(f, 0, NULL);
10448         }
10449     }
10450 }
10451
10452 char *
10453 SavePart(str)
10454      char *str;
10455 {
10456     static char buf[MSG_SIZ];
10457     char *p;
10458     
10459     p = strchr(str, ' ');
10460     if (p == NULL) return str;
10461     strncpy(buf, str, p - str);
10462     buf[p - str] = NULLCHAR;
10463     return buf;
10464 }
10465
10466 #define PGN_MAX_LINE 75
10467
10468 #define PGN_SIDE_WHITE  0
10469 #define PGN_SIDE_BLACK  1
10470
10471 /* [AS] */
10472 static int FindFirstMoveOutOfBook( int side )
10473 {
10474     int result = -1;
10475
10476     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10477         int index = backwardMostMove;
10478         int has_book_hit = 0;
10479
10480         if( (index % 2) != side ) {
10481             index++;
10482         }
10483
10484         while( index < forwardMostMove ) {
10485             /* Check to see if engine is in book */
10486             int depth = pvInfoList[index].depth;
10487             int score = pvInfoList[index].score;
10488             int in_book = 0;
10489
10490             if( depth <= 2 ) {
10491                 in_book = 1;
10492             }
10493             else if( score == 0 && depth == 63 ) {
10494                 in_book = 1; /* Zappa */
10495             }
10496             else if( score == 2 && depth == 99 ) {
10497                 in_book = 1; /* Abrok */
10498             }
10499
10500             has_book_hit += in_book;
10501
10502             if( ! in_book ) {
10503                 result = index;
10504
10505                 break;
10506             }
10507
10508             index += 2;
10509         }
10510     }
10511
10512     return result;
10513 }
10514
10515 /* [AS] */
10516 void GetOutOfBookInfo( char * buf )
10517 {
10518     int oob[2];
10519     int i;
10520     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10521
10522     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10523     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10524
10525     *buf = '\0';
10526
10527     if( oob[0] >= 0 || oob[1] >= 0 ) {
10528         for( i=0; i<2; i++ ) {
10529             int idx = oob[i];
10530
10531             if( idx >= 0 ) {
10532                 if( i > 0 && oob[0] >= 0 ) {
10533                     strcat( buf, "   " );
10534                 }
10535
10536                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10537                 sprintf( buf+strlen(buf), "%s%.2f", 
10538                     pvInfoList[idx].score >= 0 ? "+" : "",
10539                     pvInfoList[idx].score / 100.0 );
10540             }
10541         }
10542     }
10543 }
10544
10545 /* Save game in PGN style and close the file */
10546 int
10547 SaveGamePGN(f)
10548      FILE *f;
10549 {
10550     int i, offset, linelen, newblock;
10551     time_t tm;
10552 //    char *movetext;
10553     char numtext[32];
10554     int movelen, numlen, blank;
10555     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10556
10557     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10558     
10559     tm = time((time_t *) NULL);
10560     
10561     PrintPGNTags(f, &gameInfo);
10562     
10563     if (backwardMostMove > 0 || startedFromSetupPosition) {
10564         char *fen = PositionToFEN(backwardMostMove, NULL);
10565         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10566         fprintf(f, "\n{--------------\n");
10567         PrintPosition(f, backwardMostMove);
10568         fprintf(f, "--------------}\n");
10569         free(fen);
10570     }
10571     else {
10572         /* [AS] Out of book annotation */
10573         if( appData.saveOutOfBookInfo ) {
10574             char buf[64];
10575
10576             GetOutOfBookInfo( buf );
10577
10578             if( buf[0] != '\0' ) {
10579                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10580             }
10581         }
10582
10583         fprintf(f, "\n");
10584     }
10585
10586     i = backwardMostMove;
10587     linelen = 0;
10588     newblock = TRUE;
10589
10590     while (i < forwardMostMove) {
10591         /* Print comments preceding this move */
10592         if (commentList[i] != NULL) {
10593             if (linelen > 0) fprintf(f, "\n");
10594             fprintf(f, "%s", commentList[i]);
10595             linelen = 0;
10596             newblock = TRUE;
10597         }
10598
10599         /* Format move number */
10600         if ((i % 2) == 0) {
10601             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10602         } else {
10603             if (newblock) {
10604                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10605             } else {
10606                 numtext[0] = NULLCHAR;
10607             }
10608         }
10609         numlen = strlen(numtext);
10610         newblock = FALSE;
10611
10612         /* Print move number */
10613         blank = linelen > 0 && numlen > 0;
10614         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10615             fprintf(f, "\n");
10616             linelen = 0;
10617             blank = 0;
10618         }
10619         if (blank) {
10620             fprintf(f, " ");
10621             linelen++;
10622         }
10623         fprintf(f, "%s", numtext);
10624         linelen += numlen;
10625
10626         /* Get move */
10627         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10628         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10629
10630         /* Print move */
10631         blank = linelen > 0 && movelen > 0;
10632         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10633             fprintf(f, "\n");
10634             linelen = 0;
10635             blank = 0;
10636         }
10637         if (blank) {
10638             fprintf(f, " ");
10639             linelen++;
10640         }
10641         fprintf(f, "%s", move_buffer);
10642         linelen += movelen;
10643
10644         /* [AS] Add PV info if present */
10645         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10646             /* [HGM] add time */
10647             char buf[MSG_SIZ]; int seconds;
10648
10649             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10650
10651             if( seconds <= 0) buf[0] = 0; else
10652             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10653                 seconds = (seconds + 4)/10; // round to full seconds
10654                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10655                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10656             }
10657
10658             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10659                 pvInfoList[i].score >= 0 ? "+" : "",
10660                 pvInfoList[i].score / 100.0,
10661                 pvInfoList[i].depth,
10662                 buf );
10663
10664             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10665
10666             /* Print score/depth */
10667             blank = linelen > 0 && movelen > 0;
10668             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10669                 fprintf(f, "\n");
10670                 linelen = 0;
10671                 blank = 0;
10672             }
10673             if (blank) {
10674                 fprintf(f, " ");
10675                 linelen++;
10676             }
10677             fprintf(f, "%s", move_buffer);
10678             linelen += movelen;
10679         }
10680
10681         i++;
10682     }
10683     
10684     /* Start a new line */
10685     if (linelen > 0) fprintf(f, "\n");
10686
10687     /* Print comments after last move */
10688     if (commentList[i] != NULL) {
10689         fprintf(f, "%s\n", commentList[i]);
10690     }
10691
10692     /* Print result */
10693     if (gameInfo.resultDetails != NULL &&
10694         gameInfo.resultDetails[0] != NULLCHAR) {
10695         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10696                 PGNResult(gameInfo.result));
10697     } else {
10698         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10699     }
10700
10701     fclose(f);
10702     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10703     return TRUE;
10704 }
10705
10706 /* Save game in old style and close the file */
10707 int
10708 SaveGameOldStyle(f)
10709      FILE *f;
10710 {
10711     int i, offset;
10712     time_t tm;
10713     
10714     tm = time((time_t *) NULL);
10715     
10716     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10717     PrintOpponents(f);
10718     
10719     if (backwardMostMove > 0 || startedFromSetupPosition) {
10720         fprintf(f, "\n[--------------\n");
10721         PrintPosition(f, backwardMostMove);
10722         fprintf(f, "--------------]\n");
10723     } else {
10724         fprintf(f, "\n");
10725     }
10726
10727     i = backwardMostMove;
10728     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10729
10730     while (i < forwardMostMove) {
10731         if (commentList[i] != NULL) {
10732             fprintf(f, "[%s]\n", commentList[i]);
10733         }
10734
10735         if ((i % 2) == 1) {
10736             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10737             i++;
10738         } else {
10739             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10740             i++;
10741             if (commentList[i] != NULL) {
10742                 fprintf(f, "\n");
10743                 continue;
10744             }
10745             if (i >= forwardMostMove) {
10746                 fprintf(f, "\n");
10747                 break;
10748             }
10749             fprintf(f, "%s\n", parseList[i]);
10750             i++;
10751         }
10752     }
10753     
10754     if (commentList[i] != NULL) {
10755         fprintf(f, "[%s]\n", commentList[i]);
10756     }
10757
10758     /* This isn't really the old style, but it's close enough */
10759     if (gameInfo.resultDetails != NULL &&
10760         gameInfo.resultDetails[0] != NULLCHAR) {
10761         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10762                 gameInfo.resultDetails);
10763     } else {
10764         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10765     }
10766
10767     fclose(f);
10768     return TRUE;
10769 }
10770
10771 /* Save the current game to open file f and close the file */
10772 int
10773 SaveGame(f, dummy, dummy2)
10774      FILE *f;
10775      int dummy;
10776      char *dummy2;
10777 {
10778     if (gameMode == EditPosition) EditPositionDone(TRUE);
10779     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10780     if (appData.oldSaveStyle)
10781       return SaveGameOldStyle(f);
10782     else
10783       return SaveGamePGN(f);
10784 }
10785
10786 /* Save the current position to the given file */
10787 int
10788 SavePositionToFile(filename)
10789      char *filename;
10790 {
10791     FILE *f;
10792     char buf[MSG_SIZ];
10793
10794     if (strcmp(filename, "-") == 0) {
10795         return SavePosition(stdout, 0, NULL);
10796     } else {
10797         f = fopen(filename, "a");
10798         if (f == NULL) {
10799             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10800             DisplayError(buf, errno);
10801             return FALSE;
10802         } else {
10803             SavePosition(f, 0, NULL);
10804             return TRUE;
10805         }
10806     }
10807 }
10808
10809 /* Save the current position to the given open file and close the file */
10810 int
10811 SavePosition(f, dummy, dummy2)
10812      FILE *f;
10813      int dummy;
10814      char *dummy2;
10815 {
10816     time_t tm;
10817     char *fen;
10818     
10819     if (gameMode == EditPosition) EditPositionDone(TRUE);
10820     if (appData.oldSaveStyle) {
10821         tm = time((time_t *) NULL);
10822     
10823         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10824         PrintOpponents(f);
10825         fprintf(f, "[--------------\n");
10826         PrintPosition(f, currentMove);
10827         fprintf(f, "--------------]\n");
10828     } else {
10829         fen = PositionToFEN(currentMove, NULL);
10830         fprintf(f, "%s\n", fen);
10831         free(fen);
10832     }
10833     fclose(f);
10834     return TRUE;
10835 }
10836
10837 void
10838 ReloadCmailMsgEvent(unregister)
10839      int unregister;
10840 {
10841 #if !WIN32
10842     static char *inFilename = NULL;
10843     static char *outFilename;
10844     int i;
10845     struct stat inbuf, outbuf;
10846     int status;
10847     
10848     /* Any registered moves are unregistered if unregister is set, */
10849     /* i.e. invoked by the signal handler */
10850     if (unregister) {
10851         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10852             cmailMoveRegistered[i] = FALSE;
10853             if (cmailCommentList[i] != NULL) {
10854                 free(cmailCommentList[i]);
10855                 cmailCommentList[i] = NULL;
10856             }
10857         }
10858         nCmailMovesRegistered = 0;
10859     }
10860
10861     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10862         cmailResult[i] = CMAIL_NOT_RESULT;
10863     }
10864     nCmailResults = 0;
10865
10866     if (inFilename == NULL) {
10867         /* Because the filenames are static they only get malloced once  */
10868         /* and they never get freed                                      */
10869         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10870         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10871
10872         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10873         sprintf(outFilename, "%s.out", appData.cmailGameName);
10874     }
10875     
10876     status = stat(outFilename, &outbuf);
10877     if (status < 0) {
10878         cmailMailedMove = FALSE;
10879     } else {
10880         status = stat(inFilename, &inbuf);
10881         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10882     }
10883     
10884     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10885        counts the games, notes how each one terminated, etc.
10886        
10887        It would be nice to remove this kludge and instead gather all
10888        the information while building the game list.  (And to keep it
10889        in the game list nodes instead of having a bunch of fixed-size
10890        parallel arrays.)  Note this will require getting each game's
10891        termination from the PGN tags, as the game list builder does
10892        not process the game moves.  --mann
10893        */
10894     cmailMsgLoaded = TRUE;
10895     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10896     
10897     /* Load first game in the file or popup game menu */
10898     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10899
10900 #endif /* !WIN32 */
10901     return;
10902 }
10903
10904 int
10905 RegisterMove()
10906 {
10907     FILE *f;
10908     char string[MSG_SIZ];
10909
10910     if (   cmailMailedMove
10911         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10912         return TRUE;            /* Allow free viewing  */
10913     }
10914
10915     /* Unregister move to ensure that we don't leave RegisterMove        */
10916     /* with the move registered when the conditions for registering no   */
10917     /* longer hold                                                       */
10918     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10919         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10920         nCmailMovesRegistered --;
10921
10922         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10923           {
10924               free(cmailCommentList[lastLoadGameNumber - 1]);
10925               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10926           }
10927     }
10928
10929     if (cmailOldMove == -1) {
10930         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10931         return FALSE;
10932     }
10933
10934     if (currentMove > cmailOldMove + 1) {
10935         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10936         return FALSE;
10937     }
10938
10939     if (currentMove < cmailOldMove) {
10940         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10941         return FALSE;
10942     }
10943
10944     if (forwardMostMove > currentMove) {
10945         /* Silently truncate extra moves */
10946         TruncateGame();
10947     }
10948
10949     if (   (currentMove == cmailOldMove + 1)
10950         || (   (currentMove == cmailOldMove)
10951             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10952                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10953         if (gameInfo.result != GameUnfinished) {
10954             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10955         }
10956
10957         if (commentList[currentMove] != NULL) {
10958             cmailCommentList[lastLoadGameNumber - 1]
10959               = StrSave(commentList[currentMove]);
10960         }
10961         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10962
10963         if (appData.debugMode)
10964           fprintf(debugFP, "Saving %s for game %d\n",
10965                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10966
10967         sprintf(string,
10968                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10969         
10970         f = fopen(string, "w");
10971         if (appData.oldSaveStyle) {
10972             SaveGameOldStyle(f); /* also closes the file */
10973             
10974             sprintf(string, "%s.pos.out", appData.cmailGameName);
10975             f = fopen(string, "w");
10976             SavePosition(f, 0, NULL); /* also closes the file */
10977         } else {
10978             fprintf(f, "{--------------\n");
10979             PrintPosition(f, currentMove);
10980             fprintf(f, "--------------}\n\n");
10981             
10982             SaveGame(f, 0, NULL); /* also closes the file*/
10983         }
10984         
10985         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10986         nCmailMovesRegistered ++;
10987     } else if (nCmailGames == 1) {
10988         DisplayError(_("You have not made a move yet"), 0);
10989         return FALSE;
10990     }
10991
10992     return TRUE;
10993 }
10994
10995 void
10996 MailMoveEvent()
10997 {
10998 #if !WIN32
10999     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11000     FILE *commandOutput;
11001     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11002     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11003     int nBuffers;
11004     int i;
11005     int archived;
11006     char *arcDir;
11007
11008     if (! cmailMsgLoaded) {
11009         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11010         return;
11011     }
11012
11013     if (nCmailGames == nCmailResults) {
11014         DisplayError(_("No unfinished games"), 0);
11015         return;
11016     }
11017
11018 #if CMAIL_PROHIBIT_REMAIL
11019     if (cmailMailedMove) {
11020         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);
11021         DisplayError(msg, 0);
11022         return;
11023     }
11024 #endif
11025
11026     if (! (cmailMailedMove || RegisterMove())) return;
11027     
11028     if (   cmailMailedMove
11029         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11030         sprintf(string, partCommandString,
11031                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11032         commandOutput = popen(string, "r");
11033
11034         if (commandOutput == NULL) {
11035             DisplayError(_("Failed to invoke cmail"), 0);
11036         } else {
11037             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11038                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11039             }
11040             if (nBuffers > 1) {
11041                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11042                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11043                 nBytes = MSG_SIZ - 1;
11044             } else {
11045                 (void) memcpy(msg, buffer, nBytes);
11046             }
11047             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11048
11049             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11050                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11051
11052                 archived = TRUE;
11053                 for (i = 0; i < nCmailGames; i ++) {
11054                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11055                         archived = FALSE;
11056                     }
11057                 }
11058                 if (   archived
11059                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11060                         != NULL)) {
11061                     sprintf(buffer, "%s/%s.%s.archive",
11062                             arcDir,
11063                             appData.cmailGameName,
11064                             gameInfo.date);
11065                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11066                     cmailMsgLoaded = FALSE;
11067                 }
11068             }
11069
11070             DisplayInformation(msg);
11071             pclose(commandOutput);
11072         }
11073     } else {
11074         if ((*cmailMsg) != '\0') {
11075             DisplayInformation(cmailMsg);
11076         }
11077     }
11078
11079     return;
11080 #endif /* !WIN32 */
11081 }
11082
11083 char *
11084 CmailMsg()
11085 {
11086 #if WIN32
11087     return NULL;
11088 #else
11089     int  prependComma = 0;
11090     char number[5];
11091     char string[MSG_SIZ];       /* Space for game-list */
11092     int  i;
11093     
11094     if (!cmailMsgLoaded) return "";
11095
11096     if (cmailMailedMove) {
11097         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11098     } else {
11099         /* Create a list of games left */
11100         sprintf(string, "[");
11101         for (i = 0; i < nCmailGames; i ++) {
11102             if (! (   cmailMoveRegistered[i]
11103                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11104                 if (prependComma) {
11105                     sprintf(number, ",%d", i + 1);
11106                 } else {
11107                     sprintf(number, "%d", i + 1);
11108                     prependComma = 1;
11109                 }
11110                 
11111                 strcat(string, number);
11112             }
11113         }
11114         strcat(string, "]");
11115
11116         if (nCmailMovesRegistered + nCmailResults == 0) {
11117             switch (nCmailGames) {
11118               case 1:
11119                 sprintf(cmailMsg,
11120                         _("Still need to make move for game\n"));
11121                 break;
11122                 
11123               case 2:
11124                 sprintf(cmailMsg,
11125                         _("Still need to make moves for both games\n"));
11126                 break;
11127                 
11128               default:
11129                 sprintf(cmailMsg,
11130                         _("Still need to make moves for all %d games\n"),
11131                         nCmailGames);
11132                 break;
11133             }
11134         } else {
11135             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11136               case 1:
11137                 sprintf(cmailMsg,
11138                         _("Still need to make a move for game %s\n"),
11139                         string);
11140                 break;
11141                 
11142               case 0:
11143                 if (nCmailResults == nCmailGames) {
11144                     sprintf(cmailMsg, _("No unfinished games\n"));
11145                 } else {
11146                     sprintf(cmailMsg, _("Ready to send mail\n"));
11147                 }
11148                 break;
11149                 
11150               default:
11151                 sprintf(cmailMsg,
11152                         _("Still need to make moves for games %s\n"),
11153                         string);
11154             }
11155         }
11156     }
11157     return cmailMsg;
11158 #endif /* WIN32 */
11159 }
11160
11161 void
11162 ResetGameEvent()
11163 {
11164     if (gameMode == Training)
11165       SetTrainingModeOff();
11166
11167     Reset(TRUE, TRUE);
11168     cmailMsgLoaded = FALSE;
11169     if (appData.icsActive) {
11170       SendToICS(ics_prefix);
11171       SendToICS("refresh\n");
11172     }
11173 }
11174
11175 void
11176 ExitEvent(status)
11177      int status;
11178 {
11179     exiting++;
11180     if (exiting > 2) {
11181       /* Give up on clean exit */
11182       exit(status);
11183     }
11184     if (exiting > 1) {
11185       /* Keep trying for clean exit */
11186       return;
11187     }
11188
11189     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11190
11191     if (telnetISR != NULL) {
11192       RemoveInputSource(telnetISR);
11193     }
11194     if (icsPR != NoProc) {
11195       DestroyChildProcess(icsPR, TRUE);
11196     }
11197
11198     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11199     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11200
11201     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11202     /* make sure this other one finishes before killing it!                  */
11203     if(endingGame) { int count = 0;
11204         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11205         while(endingGame && count++ < 10) DoSleep(1);
11206         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11207     }
11208
11209     /* Kill off chess programs */
11210     if (first.pr != NoProc) {
11211         ExitAnalyzeMode();
11212         
11213         DoSleep( appData.delayBeforeQuit );
11214         SendToProgram("quit\n", &first);
11215         DoSleep( appData.delayAfterQuit );
11216         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11217     }
11218     if (second.pr != NoProc) {
11219         DoSleep( appData.delayBeforeQuit );
11220         SendToProgram("quit\n", &second);
11221         DoSleep( appData.delayAfterQuit );
11222         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11223     }
11224     if (first.isr != NULL) {
11225         RemoveInputSource(first.isr);
11226     }
11227     if (second.isr != NULL) {
11228         RemoveInputSource(second.isr);
11229     }
11230
11231     ShutDownFrontEnd();
11232     exit(status);
11233 }
11234
11235 void
11236 PauseEvent()
11237 {
11238     if (appData.debugMode)
11239         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11240     if (pausing) {
11241         pausing = FALSE;
11242         ModeHighlight();
11243         if (gameMode == MachinePlaysWhite ||
11244             gameMode == MachinePlaysBlack) {
11245             StartClocks();
11246         } else {
11247             DisplayBothClocks();
11248         }
11249         if (gameMode == PlayFromGameFile) {
11250             if (appData.timeDelay >= 0) 
11251                 AutoPlayGameLoop();
11252         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11253             Reset(FALSE, TRUE);
11254             SendToICS(ics_prefix);
11255             SendToICS("refresh\n");
11256         } else if (currentMove < forwardMostMove) {
11257             ForwardInner(forwardMostMove);
11258         }
11259         pauseExamInvalid = FALSE;
11260     } else {
11261         switch (gameMode) {
11262           default:
11263             return;
11264           case IcsExamining:
11265             pauseExamForwardMostMove = forwardMostMove;
11266             pauseExamInvalid = FALSE;
11267             /* fall through */
11268           case IcsObserving:
11269           case IcsPlayingWhite:
11270           case IcsPlayingBlack:
11271             pausing = TRUE;
11272             ModeHighlight();
11273             return;
11274           case PlayFromGameFile:
11275             (void) StopLoadGameTimer();
11276             pausing = TRUE;
11277             ModeHighlight();
11278             break;
11279           case BeginningOfGame:
11280             if (appData.icsActive) return;
11281             /* else fall through */
11282           case MachinePlaysWhite:
11283           case MachinePlaysBlack:
11284           case TwoMachinesPlay:
11285             if (forwardMostMove == 0)
11286               return;           /* don't pause if no one has moved */
11287             if ((gameMode == MachinePlaysWhite &&
11288                  !WhiteOnMove(forwardMostMove)) ||
11289                 (gameMode == MachinePlaysBlack &&
11290                  WhiteOnMove(forwardMostMove))) {
11291                 StopClocks();
11292             }
11293             pausing = TRUE;
11294             ModeHighlight();
11295             break;
11296         }
11297     }
11298 }
11299
11300 void
11301 EditCommentEvent()
11302 {
11303     char title[MSG_SIZ];
11304
11305     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11306         strcpy(title, _("Edit comment"));
11307     } else {
11308         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11309                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11310                 parseList[currentMove - 1]);
11311     }
11312
11313     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11314 }
11315
11316
11317 void
11318 EditTagsEvent()
11319 {
11320     char *tags = PGNTags(&gameInfo);
11321     EditTagsPopUp(tags);
11322     free(tags);
11323 }
11324
11325 void
11326 AnalyzeModeEvent()
11327 {
11328     if (appData.noChessProgram || gameMode == AnalyzeMode)
11329       return;
11330
11331     if (gameMode != AnalyzeFile) {
11332         if (!appData.icsEngineAnalyze) {
11333                EditGameEvent();
11334                if (gameMode != EditGame) return;
11335         }
11336         ResurrectChessProgram();
11337         SendToProgram("analyze\n", &first);
11338         first.analyzing = TRUE;
11339         /*first.maybeThinking = TRUE;*/
11340         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11341         EngineOutputPopUp();
11342     }
11343     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11344     pausing = FALSE;
11345     ModeHighlight();
11346     SetGameInfo();
11347
11348     StartAnalysisClock();
11349     GetTimeMark(&lastNodeCountTime);
11350     lastNodeCount = 0;
11351 }
11352
11353 void
11354 AnalyzeFileEvent()
11355 {
11356     if (appData.noChessProgram || gameMode == AnalyzeFile)
11357       return;
11358
11359     if (gameMode != AnalyzeMode) {
11360         EditGameEvent();
11361         if (gameMode != EditGame) return;
11362         ResurrectChessProgram();
11363         SendToProgram("analyze\n", &first);
11364         first.analyzing = TRUE;
11365         /*first.maybeThinking = TRUE;*/
11366         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11367         EngineOutputPopUp();
11368     }
11369     gameMode = AnalyzeFile;
11370     pausing = FALSE;
11371     ModeHighlight();
11372     SetGameInfo();
11373
11374     StartAnalysisClock();
11375     GetTimeMark(&lastNodeCountTime);
11376     lastNodeCount = 0;
11377 }
11378
11379 void
11380 MachineWhiteEvent()
11381 {
11382     char buf[MSG_SIZ];
11383     char *bookHit = NULL;
11384
11385     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11386       return;
11387
11388
11389     if (gameMode == PlayFromGameFile || 
11390         gameMode == TwoMachinesPlay  || 
11391         gameMode == Training         || 
11392         gameMode == AnalyzeMode      || 
11393         gameMode == EndOfGame)
11394         EditGameEvent();
11395
11396     if (gameMode == EditPosition) 
11397         EditPositionDone(TRUE);
11398
11399     if (!WhiteOnMove(currentMove)) {
11400         DisplayError(_("It is not White's turn"), 0);
11401         return;
11402     }
11403   
11404     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11405       ExitAnalyzeMode();
11406
11407     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11408         gameMode == AnalyzeFile)
11409         TruncateGame();
11410
11411     ResurrectChessProgram();    /* in case it isn't running */
11412     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11413         gameMode = MachinePlaysWhite;
11414         ResetClocks();
11415     } else
11416     gameMode = MachinePlaysWhite;
11417     pausing = FALSE;
11418     ModeHighlight();
11419     SetGameInfo();
11420     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11421     DisplayTitle(buf);
11422     if (first.sendName) {
11423       sprintf(buf, "name %s\n", gameInfo.black);
11424       SendToProgram(buf, &first);
11425     }
11426     if (first.sendTime) {
11427       if (first.useColors) {
11428         SendToProgram("black\n", &first); /*gnu kludge*/
11429       }
11430       SendTimeRemaining(&first, TRUE);
11431     }
11432     if (first.useColors) {
11433       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11434     }
11435     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11436     SetMachineThinkingEnables();
11437     first.maybeThinking = TRUE;
11438     StartClocks();
11439     firstMove = FALSE;
11440
11441     if (appData.autoFlipView && !flipView) {
11442       flipView = !flipView;
11443       DrawPosition(FALSE, NULL);
11444       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11445     }
11446
11447     if(bookHit) { // [HGM] book: simulate book reply
11448         static char bookMove[MSG_SIZ]; // a bit generous?
11449
11450         programStats.nodes = programStats.depth = programStats.time = 
11451         programStats.score = programStats.got_only_move = 0;
11452         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11453
11454         strcpy(bookMove, "move ");
11455         strcat(bookMove, bookHit);
11456         HandleMachineMove(bookMove, &first);
11457     }
11458 }
11459
11460 void
11461 MachineBlackEvent()
11462 {
11463     char buf[MSG_SIZ];
11464    char *bookHit = NULL;
11465
11466     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11467         return;
11468
11469
11470     if (gameMode == PlayFromGameFile || 
11471         gameMode == TwoMachinesPlay  || 
11472         gameMode == Training         || 
11473         gameMode == AnalyzeMode      || 
11474         gameMode == EndOfGame)
11475         EditGameEvent();
11476
11477     if (gameMode == EditPosition) 
11478         EditPositionDone(TRUE);
11479
11480     if (WhiteOnMove(currentMove)) {
11481         DisplayError(_("It is not Black's turn"), 0);
11482         return;
11483     }
11484     
11485     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11486       ExitAnalyzeMode();
11487
11488     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11489         gameMode == AnalyzeFile)
11490         TruncateGame();
11491
11492     ResurrectChessProgram();    /* in case it isn't running */
11493     gameMode = MachinePlaysBlack;
11494     pausing = FALSE;
11495     ModeHighlight();
11496     SetGameInfo();
11497     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11498     DisplayTitle(buf);
11499     if (first.sendName) {
11500       sprintf(buf, "name %s\n", gameInfo.white);
11501       SendToProgram(buf, &first);
11502     }
11503     if (first.sendTime) {
11504       if (first.useColors) {
11505         SendToProgram("white\n", &first); /*gnu kludge*/
11506       }
11507       SendTimeRemaining(&first, FALSE);
11508     }
11509     if (first.useColors) {
11510       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11511     }
11512     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11513     SetMachineThinkingEnables();
11514     first.maybeThinking = TRUE;
11515     StartClocks();
11516
11517     if (appData.autoFlipView && flipView) {
11518       flipView = !flipView;
11519       DrawPosition(FALSE, NULL);
11520       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11521     }
11522     if(bookHit) { // [HGM] book: simulate book reply
11523         static char bookMove[MSG_SIZ]; // a bit generous?
11524
11525         programStats.nodes = programStats.depth = programStats.time = 
11526         programStats.score = programStats.got_only_move = 0;
11527         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11528
11529         strcpy(bookMove, "move ");
11530         strcat(bookMove, bookHit);
11531         HandleMachineMove(bookMove, &first);
11532     }
11533 }
11534
11535
11536 void
11537 DisplayTwoMachinesTitle()
11538 {
11539     char buf[MSG_SIZ];
11540     if (appData.matchGames > 0) {
11541         if (first.twoMachinesColor[0] == 'w') {
11542             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11543                     gameInfo.white, gameInfo.black,
11544                     first.matchWins, second.matchWins,
11545                     matchGame - 1 - (first.matchWins + second.matchWins));
11546         } else {
11547             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11548                     gameInfo.white, gameInfo.black,
11549                     second.matchWins, first.matchWins,
11550                     matchGame - 1 - (first.matchWins + second.matchWins));
11551         }
11552     } else {
11553         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11554     }
11555     DisplayTitle(buf);
11556 }
11557
11558 void
11559 TwoMachinesEvent P((void))
11560 {
11561     int i;
11562     char buf[MSG_SIZ];
11563     ChessProgramState *onmove;
11564     char *bookHit = NULL;
11565     
11566     if (appData.noChessProgram) return;
11567
11568     switch (gameMode) {
11569       case TwoMachinesPlay:
11570         return;
11571       case MachinePlaysWhite:
11572       case MachinePlaysBlack:
11573         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11574             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11575             return;
11576         }
11577         /* fall through */
11578       case BeginningOfGame:
11579       case PlayFromGameFile:
11580       case EndOfGame:
11581         EditGameEvent();
11582         if (gameMode != EditGame) return;
11583         break;
11584       case EditPosition:
11585         EditPositionDone(TRUE);
11586         break;
11587       case AnalyzeMode:
11588       case AnalyzeFile:
11589         ExitAnalyzeMode();
11590         break;
11591       case EditGame:
11592       default:
11593         break;
11594     }
11595
11596 //    forwardMostMove = currentMove;
11597     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11598     ResurrectChessProgram();    /* in case first program isn't running */
11599
11600     if (second.pr == NULL) {
11601         StartChessProgram(&second);
11602         if (second.protocolVersion == 1) {
11603           TwoMachinesEventIfReady();
11604         } else {
11605           /* kludge: allow timeout for initial "feature" command */
11606           FreezeUI();
11607           DisplayMessage("", _("Starting second chess program"));
11608           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11609         }
11610         return;
11611     }
11612     DisplayMessage("", "");
11613     InitChessProgram(&second, FALSE);
11614     SendToProgram("force\n", &second);
11615     if (startedFromSetupPosition) {
11616         SendBoard(&second, backwardMostMove);
11617     if (appData.debugMode) {
11618         fprintf(debugFP, "Two Machines\n");
11619     }
11620     }
11621     for (i = backwardMostMove; i < forwardMostMove; i++) {
11622         SendMoveToProgram(i, &second);
11623     }
11624
11625     gameMode = TwoMachinesPlay;
11626     pausing = FALSE;
11627     ModeHighlight();
11628     SetGameInfo();
11629     DisplayTwoMachinesTitle();
11630     firstMove = TRUE;
11631     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11632         onmove = &first;
11633     } else {
11634         onmove = &second;
11635     }
11636
11637     SendToProgram(first.computerString, &first);
11638     if (first.sendName) {
11639       sprintf(buf, "name %s\n", second.tidy);
11640       SendToProgram(buf, &first);
11641     }
11642     SendToProgram(second.computerString, &second);
11643     if (second.sendName) {
11644       sprintf(buf, "name %s\n", first.tidy);
11645       SendToProgram(buf, &second);
11646     }
11647
11648     ResetClocks();
11649     if (!first.sendTime || !second.sendTime) {
11650         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11651         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11652     }
11653     if (onmove->sendTime) {
11654       if (onmove->useColors) {
11655         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11656       }
11657       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11658     }
11659     if (onmove->useColors) {
11660       SendToProgram(onmove->twoMachinesColor, onmove);
11661     }
11662     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11663 //    SendToProgram("go\n", onmove);
11664     onmove->maybeThinking = TRUE;
11665     SetMachineThinkingEnables();
11666
11667     StartClocks();
11668
11669     if(bookHit) { // [HGM] book: simulate book reply
11670         static char bookMove[MSG_SIZ]; // a bit generous?
11671
11672         programStats.nodes = programStats.depth = programStats.time = 
11673         programStats.score = programStats.got_only_move = 0;
11674         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11675
11676         strcpy(bookMove, "move ");
11677         strcat(bookMove, bookHit);
11678         savedMessage = bookMove; // args for deferred call
11679         savedState = onmove;
11680         ScheduleDelayedEvent(DeferredBookMove, 1);
11681     }
11682 }
11683
11684 void
11685 TrainingEvent()
11686 {
11687     if (gameMode == Training) {
11688       SetTrainingModeOff();
11689       gameMode = PlayFromGameFile;
11690       DisplayMessage("", _("Training mode off"));
11691     } else {
11692       gameMode = Training;
11693       animateTraining = appData.animate;
11694
11695       /* make sure we are not already at the end of the game */
11696       if (currentMove < forwardMostMove) {
11697         SetTrainingModeOn();
11698         DisplayMessage("", _("Training mode on"));
11699       } else {
11700         gameMode = PlayFromGameFile;
11701         DisplayError(_("Already at end of game"), 0);
11702       }
11703     }
11704     ModeHighlight();
11705 }
11706
11707 void
11708 IcsClientEvent()
11709 {
11710     if (!appData.icsActive) return;
11711     switch (gameMode) {
11712       case IcsPlayingWhite:
11713       case IcsPlayingBlack:
11714       case IcsObserving:
11715       case IcsIdle:
11716       case BeginningOfGame:
11717       case IcsExamining:
11718         return;
11719
11720       case EditGame:
11721         break;
11722
11723       case EditPosition:
11724         EditPositionDone(TRUE);
11725         break;
11726
11727       case AnalyzeMode:
11728       case AnalyzeFile:
11729         ExitAnalyzeMode();
11730         break;
11731         
11732       default:
11733         EditGameEvent();
11734         break;
11735     }
11736
11737     gameMode = IcsIdle;
11738     ModeHighlight();
11739     return;
11740 }
11741
11742
11743 void
11744 EditGameEvent()
11745 {
11746     int i;
11747
11748     switch (gameMode) {
11749       case Training:
11750         SetTrainingModeOff();
11751         break;
11752       case MachinePlaysWhite:
11753       case MachinePlaysBlack:
11754       case BeginningOfGame:
11755         SendToProgram("force\n", &first);
11756         SetUserThinkingEnables();
11757         break;
11758       case PlayFromGameFile:
11759         (void) StopLoadGameTimer();
11760         if (gameFileFP != NULL) {
11761             gameFileFP = NULL;
11762         }
11763         break;
11764       case EditPosition:
11765         EditPositionDone(TRUE);
11766         break;
11767       case AnalyzeMode:
11768       case AnalyzeFile:
11769         ExitAnalyzeMode();
11770         SendToProgram("force\n", &first);
11771         break;
11772       case TwoMachinesPlay:
11773         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11774         ResurrectChessProgram();
11775         SetUserThinkingEnables();
11776         break;
11777       case EndOfGame:
11778         ResurrectChessProgram();
11779         break;
11780       case IcsPlayingBlack:
11781       case IcsPlayingWhite:
11782         DisplayError(_("Warning: You are still playing a game"), 0);
11783         break;
11784       case IcsObserving:
11785         DisplayError(_("Warning: You are still observing a game"), 0);
11786         break;
11787       case IcsExamining:
11788         DisplayError(_("Warning: You are still examining a game"), 0);
11789         break;
11790       case IcsIdle:
11791         break;
11792       case EditGame:
11793       default:
11794         return;
11795     }
11796     
11797     pausing = FALSE;
11798     StopClocks();
11799     first.offeredDraw = second.offeredDraw = 0;
11800
11801     if (gameMode == PlayFromGameFile) {
11802         whiteTimeRemaining = timeRemaining[0][currentMove];
11803         blackTimeRemaining = timeRemaining[1][currentMove];
11804         DisplayTitle("");
11805     }
11806
11807     if (gameMode == MachinePlaysWhite ||
11808         gameMode == MachinePlaysBlack ||
11809         gameMode == TwoMachinesPlay ||
11810         gameMode == EndOfGame) {
11811         i = forwardMostMove;
11812         while (i > currentMove) {
11813             SendToProgram("undo\n", &first);
11814             i--;
11815         }
11816         whiteTimeRemaining = timeRemaining[0][currentMove];
11817         blackTimeRemaining = timeRemaining[1][currentMove];
11818         DisplayBothClocks();
11819         if (whiteFlag || blackFlag) {
11820             whiteFlag = blackFlag = 0;
11821         }
11822         DisplayTitle("");
11823     }           
11824     
11825     gameMode = EditGame;
11826     ModeHighlight();
11827     SetGameInfo();
11828 }
11829
11830
11831 void
11832 EditPositionEvent()
11833 {
11834     if (gameMode == EditPosition) {
11835         EditGameEvent();
11836         return;
11837     }
11838     
11839     EditGameEvent();
11840     if (gameMode != EditGame) return;
11841     
11842     gameMode = EditPosition;
11843     ModeHighlight();
11844     SetGameInfo();
11845     if (currentMove > 0)
11846       CopyBoard(boards[0], boards[currentMove]);
11847     
11848     blackPlaysFirst = !WhiteOnMove(currentMove);
11849     ResetClocks();
11850     currentMove = forwardMostMove = backwardMostMove = 0;
11851     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11852     DisplayMove(-1);
11853 }
11854
11855 void
11856 ExitAnalyzeMode()
11857 {
11858     /* [DM] icsEngineAnalyze - possible call from other functions */
11859     if (appData.icsEngineAnalyze) {
11860         appData.icsEngineAnalyze = FALSE;
11861
11862         DisplayMessage("",_("Close ICS engine analyze..."));
11863     }
11864     if (first.analysisSupport && first.analyzing) {
11865       SendToProgram("exit\n", &first);
11866       first.analyzing = FALSE;
11867     }
11868     thinkOutput[0] = NULLCHAR;
11869 }
11870
11871 void
11872 EditPositionDone(Boolean fakeRights)
11873 {
11874     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11875
11876     startedFromSetupPosition = TRUE;
11877     InitChessProgram(&first, FALSE);
11878     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11879       boards[0][EP_STATUS] = EP_NONE;
11880       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11881     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11882         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11883         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11884       } else boards[0][CASTLING][2] = NoRights;
11885     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11886         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11887         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11888       } else boards[0][CASTLING][5] = NoRights;
11889     }
11890     SendToProgram("force\n", &first);
11891     if (blackPlaysFirst) {
11892         strcpy(moveList[0], "");
11893         strcpy(parseList[0], "");
11894         currentMove = forwardMostMove = backwardMostMove = 1;
11895         CopyBoard(boards[1], boards[0]);
11896     } else {
11897         currentMove = forwardMostMove = backwardMostMove = 0;
11898     }
11899     SendBoard(&first, forwardMostMove);
11900     if (appData.debugMode) {
11901         fprintf(debugFP, "EditPosDone\n");
11902     }
11903     DisplayTitle("");
11904     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11905     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11906     gameMode = EditGame;
11907     ModeHighlight();
11908     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11909     ClearHighlights(); /* [AS] */
11910 }
11911
11912 /* Pause for `ms' milliseconds */
11913 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11914 void
11915 TimeDelay(ms)
11916      long ms;
11917 {
11918     TimeMark m1, m2;
11919
11920     GetTimeMark(&m1);
11921     do {
11922         GetTimeMark(&m2);
11923     } while (SubtractTimeMarks(&m2, &m1) < ms);
11924 }
11925
11926 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11927 void
11928 SendMultiLineToICS(buf)
11929      char *buf;
11930 {
11931     char temp[MSG_SIZ+1], *p;
11932     int len;
11933
11934     len = strlen(buf);
11935     if (len > MSG_SIZ)
11936       len = MSG_SIZ;
11937   
11938     strncpy(temp, buf, len);
11939     temp[len] = 0;
11940
11941     p = temp;
11942     while (*p) {
11943         if (*p == '\n' || *p == '\r')
11944           *p = ' ';
11945         ++p;
11946     }
11947
11948     strcat(temp, "\n");
11949     SendToICS(temp);
11950     SendToPlayer(temp, strlen(temp));
11951 }
11952
11953 void
11954 SetWhiteToPlayEvent()
11955 {
11956     if (gameMode == EditPosition) {
11957         blackPlaysFirst = FALSE;
11958         DisplayBothClocks();    /* works because currentMove is 0 */
11959     } else if (gameMode == IcsExamining) {
11960         SendToICS(ics_prefix);
11961         SendToICS("tomove white\n");
11962     }
11963 }
11964
11965 void
11966 SetBlackToPlayEvent()
11967 {
11968     if (gameMode == EditPosition) {
11969         blackPlaysFirst = TRUE;
11970         currentMove = 1;        /* kludge */
11971         DisplayBothClocks();
11972         currentMove = 0;
11973     } else if (gameMode == IcsExamining) {
11974         SendToICS(ics_prefix);
11975         SendToICS("tomove black\n");
11976     }
11977 }
11978
11979 void
11980 EditPositionMenuEvent(selection, x, y)
11981      ChessSquare selection;
11982      int x, y;
11983 {
11984     char buf[MSG_SIZ];
11985     ChessSquare piece = boards[0][y][x];
11986
11987     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11988
11989     switch (selection) {
11990       case ClearBoard:
11991         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11992             SendToICS(ics_prefix);
11993             SendToICS("bsetup clear\n");
11994         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11995             SendToICS(ics_prefix);
11996             SendToICS("clearboard\n");
11997         } else {
11998             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11999                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12000                 for (y = 0; y < BOARD_HEIGHT; y++) {
12001                     if (gameMode == IcsExamining) {
12002                         if (boards[currentMove][y][x] != EmptySquare) {
12003                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12004                                     AAA + x, ONE + y);
12005                             SendToICS(buf);
12006                         }
12007                     } else {
12008                         boards[0][y][x] = p;
12009                     }
12010                 }
12011             }
12012         }
12013         if (gameMode == EditPosition) {
12014             DrawPosition(FALSE, boards[0]);
12015         }
12016         break;
12017
12018       case WhitePlay:
12019         SetWhiteToPlayEvent();
12020         break;
12021
12022       case BlackPlay:
12023         SetBlackToPlayEvent();
12024         break;
12025
12026       case EmptySquare:
12027         if (gameMode == IcsExamining) {
12028             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12029             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12030             SendToICS(buf);
12031         } else {
12032             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12033                 if(x == BOARD_LEFT-2) {
12034                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12035                     boards[0][y][1] = 0;
12036                 } else
12037                 if(x == BOARD_RGHT+1) {
12038                     if(y >= gameInfo.holdingsSize) break;
12039                     boards[0][y][BOARD_WIDTH-2] = 0;
12040                 } else break;
12041             }
12042             boards[0][y][x] = EmptySquare;
12043             DrawPosition(FALSE, boards[0]);
12044         }
12045         break;
12046
12047       case PromotePiece:
12048         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12049            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12050             selection = (ChessSquare) (PROMOTED piece);
12051         } else if(piece == EmptySquare) selection = WhiteSilver;
12052         else selection = (ChessSquare)((int)piece - 1);
12053         goto defaultlabel;
12054
12055       case DemotePiece:
12056         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12057            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12058             selection = (ChessSquare) (DEMOTED piece);
12059         } else if(piece == EmptySquare) selection = BlackSilver;
12060         else selection = (ChessSquare)((int)piece + 1);       
12061         goto defaultlabel;
12062
12063       case WhiteQueen:
12064       case BlackQueen:
12065         if(gameInfo.variant == VariantShatranj ||
12066            gameInfo.variant == VariantXiangqi  ||
12067            gameInfo.variant == VariantCourier  ||
12068            gameInfo.variant == VariantMakruk     )
12069             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12070         goto defaultlabel;
12071
12072       case WhiteKing:
12073       case BlackKing:
12074         if(gameInfo.variant == VariantXiangqi)
12075             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12076         if(gameInfo.variant == VariantKnightmate)
12077             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12078       default:
12079         defaultlabel:
12080         if (gameMode == IcsExamining) {
12081             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12082             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12083                     PieceToChar(selection), AAA + x, ONE + y);
12084             SendToICS(buf);
12085         } else {
12086             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12087                 int n;
12088                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12089                     n = PieceToNumber(selection - BlackPawn);
12090                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12091                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12092                     boards[0][BOARD_HEIGHT-1-n][1]++;
12093                 } else
12094                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12095                     n = PieceToNumber(selection);
12096                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12097                     boards[0][n][BOARD_WIDTH-1] = selection;
12098                     boards[0][n][BOARD_WIDTH-2]++;
12099                 }
12100             } else
12101             boards[0][y][x] = selection;
12102             DrawPosition(TRUE, boards[0]);
12103         }
12104         break;
12105     }
12106 }
12107
12108
12109 void
12110 DropMenuEvent(selection, x, y)
12111      ChessSquare selection;
12112      int x, y;
12113 {
12114     ChessMove moveType;
12115
12116     switch (gameMode) {
12117       case IcsPlayingWhite:
12118       case MachinePlaysBlack:
12119         if (!WhiteOnMove(currentMove)) {
12120             DisplayMoveError(_("It is Black's turn"));
12121             return;
12122         }
12123         moveType = WhiteDrop;
12124         break;
12125       case IcsPlayingBlack:
12126       case MachinePlaysWhite:
12127         if (WhiteOnMove(currentMove)) {
12128             DisplayMoveError(_("It is White's turn"));
12129             return;
12130         }
12131         moveType = BlackDrop;
12132         break;
12133       case EditGame:
12134         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12135         break;
12136       default:
12137         return;
12138     }
12139
12140     if (moveType == BlackDrop && selection < BlackPawn) {
12141       selection = (ChessSquare) ((int) selection
12142                                  + (int) BlackPawn - (int) WhitePawn);
12143     }
12144     if (boards[currentMove][y][x] != EmptySquare) {
12145         DisplayMoveError(_("That square is occupied"));
12146         return;
12147     }
12148
12149     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12150 }
12151
12152 void
12153 AcceptEvent()
12154 {
12155     /* Accept a pending offer of any kind from opponent */
12156     
12157     if (appData.icsActive) {
12158         SendToICS(ics_prefix);
12159         SendToICS("accept\n");
12160     } else if (cmailMsgLoaded) {
12161         if (currentMove == cmailOldMove &&
12162             commentList[cmailOldMove] != NULL &&
12163             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12164                    "Black offers a draw" : "White offers a draw")) {
12165             TruncateGame();
12166             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12167             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12168         } else {
12169             DisplayError(_("There is no pending offer on this move"), 0);
12170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12171         }
12172     } else {
12173         /* Not used for offers from chess program */
12174     }
12175 }
12176
12177 void
12178 DeclineEvent()
12179 {
12180     /* Decline a pending offer of any kind from opponent */
12181     
12182     if (appData.icsActive) {
12183         SendToICS(ics_prefix);
12184         SendToICS("decline\n");
12185     } else if (cmailMsgLoaded) {
12186         if (currentMove == cmailOldMove &&
12187             commentList[cmailOldMove] != NULL &&
12188             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12189                    "Black offers a draw" : "White offers a draw")) {
12190 #ifdef NOTDEF
12191             AppendComment(cmailOldMove, "Draw declined", TRUE);
12192             DisplayComment(cmailOldMove - 1, "Draw declined");
12193 #endif /*NOTDEF*/
12194         } else {
12195             DisplayError(_("There is no pending offer on this move"), 0);
12196         }
12197     } else {
12198         /* Not used for offers from chess program */
12199     }
12200 }
12201
12202 void
12203 RematchEvent()
12204 {
12205     /* Issue ICS rematch command */
12206     if (appData.icsActive) {
12207         SendToICS(ics_prefix);
12208         SendToICS("rematch\n");
12209     }
12210 }
12211
12212 void
12213 CallFlagEvent()
12214 {
12215     /* Call your opponent's flag (claim a win on time) */
12216     if (appData.icsActive) {
12217         SendToICS(ics_prefix);
12218         SendToICS("flag\n");
12219     } else {
12220         switch (gameMode) {
12221           default:
12222             return;
12223           case MachinePlaysWhite:
12224             if (whiteFlag) {
12225                 if (blackFlag)
12226                   GameEnds(GameIsDrawn, "Both players ran out of time",
12227                            GE_PLAYER);
12228                 else
12229                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12230             } else {
12231                 DisplayError(_("Your opponent is not out of time"), 0);
12232             }
12233             break;
12234           case MachinePlaysBlack:
12235             if (blackFlag) {
12236                 if (whiteFlag)
12237                   GameEnds(GameIsDrawn, "Both players ran out of time",
12238                            GE_PLAYER);
12239                 else
12240                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12241             } else {
12242                 DisplayError(_("Your opponent is not out of time"), 0);
12243             }
12244             break;
12245         }
12246     }
12247 }
12248
12249 void
12250 DrawEvent()
12251 {
12252     /* Offer draw or accept pending draw offer from opponent */
12253     
12254     if (appData.icsActive) {
12255         /* Note: tournament rules require draw offers to be
12256            made after you make your move but before you punch
12257            your clock.  Currently ICS doesn't let you do that;
12258            instead, you immediately punch your clock after making
12259            a move, but you can offer a draw at any time. */
12260         
12261         SendToICS(ics_prefix);
12262         SendToICS("draw\n");
12263         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12264     } else if (cmailMsgLoaded) {
12265         if (currentMove == cmailOldMove &&
12266             commentList[cmailOldMove] != NULL &&
12267             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12268                    "Black offers a draw" : "White offers a draw")) {
12269             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12270             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12271         } else if (currentMove == cmailOldMove + 1) {
12272             char *offer = WhiteOnMove(cmailOldMove) ?
12273               "White offers a draw" : "Black offers a draw";
12274             AppendComment(currentMove, offer, TRUE);
12275             DisplayComment(currentMove - 1, offer);
12276             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12277         } else {
12278             DisplayError(_("You must make your move before offering a draw"), 0);
12279             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12280         }
12281     } else if (first.offeredDraw) {
12282         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12283     } else {
12284         if (first.sendDrawOffers) {
12285             SendToProgram("draw\n", &first);
12286             userOfferedDraw = TRUE;
12287         }
12288     }
12289 }
12290
12291 void
12292 AdjournEvent()
12293 {
12294     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12295     
12296     if (appData.icsActive) {
12297         SendToICS(ics_prefix);
12298         SendToICS("adjourn\n");
12299     } else {
12300         /* Currently GNU Chess doesn't offer or accept Adjourns */
12301     }
12302 }
12303
12304
12305 void
12306 AbortEvent()
12307 {
12308     /* Offer Abort or accept pending Abort offer from opponent */
12309     
12310     if (appData.icsActive) {
12311         SendToICS(ics_prefix);
12312         SendToICS("abort\n");
12313     } else {
12314         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12315     }
12316 }
12317
12318 void
12319 ResignEvent()
12320 {
12321     /* Resign.  You can do this even if it's not your turn. */
12322     
12323     if (appData.icsActive) {
12324         SendToICS(ics_prefix);
12325         SendToICS("resign\n");
12326     } else {
12327         switch (gameMode) {
12328           case MachinePlaysWhite:
12329             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12330             break;
12331           case MachinePlaysBlack:
12332             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12333             break;
12334           case EditGame:
12335             if (cmailMsgLoaded) {
12336                 TruncateGame();
12337                 if (WhiteOnMove(cmailOldMove)) {
12338                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12339                 } else {
12340                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12341                 }
12342                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12343             }
12344             break;
12345           default:
12346             break;
12347         }
12348     }
12349 }
12350
12351
12352 void
12353 StopObservingEvent()
12354 {
12355     /* Stop observing current games */
12356     SendToICS(ics_prefix);
12357     SendToICS("unobserve\n");
12358 }
12359
12360 void
12361 StopExaminingEvent()
12362 {
12363     /* Stop observing current game */
12364     SendToICS(ics_prefix);
12365     SendToICS("unexamine\n");
12366 }
12367
12368 void
12369 ForwardInner(target)
12370      int target;
12371 {
12372     int limit;
12373
12374     if (appData.debugMode)
12375         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12376                 target, currentMove, forwardMostMove);
12377
12378     if (gameMode == EditPosition)
12379       return;
12380
12381     if (gameMode == PlayFromGameFile && !pausing)
12382       PauseEvent();
12383     
12384     if (gameMode == IcsExamining && pausing)
12385       limit = pauseExamForwardMostMove;
12386     else
12387       limit = forwardMostMove;
12388     
12389     if (target > limit) target = limit;
12390
12391     if (target > 0 && moveList[target - 1][0]) {
12392         int fromX, fromY, toX, toY;
12393         toX = moveList[target - 1][2] - AAA;
12394         toY = moveList[target - 1][3] - ONE;
12395         if (moveList[target - 1][1] == '@') {
12396             if (appData.highlightLastMove) {
12397                 SetHighlights(-1, -1, toX, toY);
12398             }
12399         } else {
12400             fromX = moveList[target - 1][0] - AAA;
12401             fromY = moveList[target - 1][1] - ONE;
12402             if (target == currentMove + 1) {
12403                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12404             }
12405             if (appData.highlightLastMove) {
12406                 SetHighlights(fromX, fromY, toX, toY);
12407             }
12408         }
12409     }
12410     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12411         gameMode == Training || gameMode == PlayFromGameFile || 
12412         gameMode == AnalyzeFile) {
12413         while (currentMove < target) {
12414             SendMoveToProgram(currentMove++, &first);
12415         }
12416     } else {
12417         currentMove = target;
12418     }
12419     
12420     if (gameMode == EditGame || gameMode == EndOfGame) {
12421         whiteTimeRemaining = timeRemaining[0][currentMove];
12422         blackTimeRemaining = timeRemaining[1][currentMove];
12423     }
12424     DisplayBothClocks();
12425     DisplayMove(currentMove - 1);
12426     DrawPosition(FALSE, boards[currentMove]);
12427     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12428     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12429         DisplayComment(currentMove - 1, commentList[currentMove]);
12430     }
12431 }
12432
12433
12434 void
12435 ForwardEvent()
12436 {
12437     if (gameMode == IcsExamining && !pausing) {
12438         SendToICS(ics_prefix);
12439         SendToICS("forward\n");
12440     } else {
12441         ForwardInner(currentMove + 1);
12442     }
12443 }
12444
12445 void
12446 ToEndEvent()
12447 {
12448     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12449         /* to optimze, we temporarily turn off analysis mode while we feed
12450          * the remaining moves to the engine. Otherwise we get analysis output
12451          * after each move.
12452          */ 
12453         if (first.analysisSupport) {
12454           SendToProgram("exit\nforce\n", &first);
12455           first.analyzing = FALSE;
12456         }
12457     }
12458         
12459     if (gameMode == IcsExamining && !pausing) {
12460         SendToICS(ics_prefix);
12461         SendToICS("forward 999999\n");
12462     } else {
12463         ForwardInner(forwardMostMove);
12464     }
12465
12466     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12467         /* we have fed all the moves, so reactivate analysis mode */
12468         SendToProgram("analyze\n", &first);
12469         first.analyzing = TRUE;
12470         /*first.maybeThinking = TRUE;*/
12471         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12472     }
12473 }
12474
12475 void
12476 BackwardInner(target)
12477      int target;
12478 {
12479     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12480
12481     if (appData.debugMode)
12482         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12483                 target, currentMove, forwardMostMove);
12484
12485     if (gameMode == EditPosition) return;
12486     if (currentMove <= backwardMostMove) {
12487         ClearHighlights();
12488         DrawPosition(full_redraw, boards[currentMove]);
12489         return;
12490     }
12491     if (gameMode == PlayFromGameFile && !pausing)
12492       PauseEvent();
12493     
12494     if (moveList[target][0]) {
12495         int fromX, fromY, toX, toY;
12496         toX = moveList[target][2] - AAA;
12497         toY = moveList[target][3] - ONE;
12498         if (moveList[target][1] == '@') {
12499             if (appData.highlightLastMove) {
12500                 SetHighlights(-1, -1, toX, toY);
12501             }
12502         } else {
12503             fromX = moveList[target][0] - AAA;
12504             fromY = moveList[target][1] - ONE;
12505             if (target == currentMove - 1) {
12506                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12507             }
12508             if (appData.highlightLastMove) {
12509                 SetHighlights(fromX, fromY, toX, toY);
12510             }
12511         }
12512     }
12513     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12514         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12515         while (currentMove > target) {
12516             SendToProgram("undo\n", &first);
12517             currentMove--;
12518         }
12519     } else {
12520         currentMove = target;
12521     }
12522     
12523     if (gameMode == EditGame || gameMode == EndOfGame) {
12524         whiteTimeRemaining = timeRemaining[0][currentMove];
12525         blackTimeRemaining = timeRemaining[1][currentMove];
12526     }
12527     DisplayBothClocks();
12528     DisplayMove(currentMove - 1);
12529     DrawPosition(full_redraw, boards[currentMove]);
12530     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12531     // [HGM] PV info: routine tests if comment empty
12532     DisplayComment(currentMove - 1, commentList[currentMove]);
12533 }
12534
12535 void
12536 BackwardEvent()
12537 {
12538     if (gameMode == IcsExamining && !pausing) {
12539         SendToICS(ics_prefix);
12540         SendToICS("backward\n");
12541     } else {
12542         BackwardInner(currentMove - 1);
12543     }
12544 }
12545
12546 void
12547 ToStartEvent()
12548 {
12549     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12550         /* to optimize, we temporarily turn off analysis mode while we undo
12551          * all the moves. Otherwise we get analysis output after each undo.
12552          */ 
12553         if (first.analysisSupport) {
12554           SendToProgram("exit\nforce\n", &first);
12555           first.analyzing = FALSE;
12556         }
12557     }
12558
12559     if (gameMode == IcsExamining && !pausing) {
12560         SendToICS(ics_prefix);
12561         SendToICS("backward 999999\n");
12562     } else {
12563         BackwardInner(backwardMostMove);
12564     }
12565
12566     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12567         /* we have fed all the moves, so reactivate analysis mode */
12568         SendToProgram("analyze\n", &first);
12569         first.analyzing = TRUE;
12570         /*first.maybeThinking = TRUE;*/
12571         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12572     }
12573 }
12574
12575 void
12576 ToNrEvent(int to)
12577 {
12578   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12579   if (to >= forwardMostMove) to = forwardMostMove;
12580   if (to <= backwardMostMove) to = backwardMostMove;
12581   if (to < currentMove) {
12582     BackwardInner(to);
12583   } else {
12584     ForwardInner(to);
12585   }
12586 }
12587
12588 void
12589 RevertEvent()
12590 {
12591     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12592         return;
12593     }
12594     if (gameMode != IcsExamining) {
12595         DisplayError(_("You are not examining a game"), 0);
12596         return;
12597     }
12598     if (pausing) {
12599         DisplayError(_("You can't revert while pausing"), 0);
12600         return;
12601     }
12602     SendToICS(ics_prefix);
12603     SendToICS("revert\n");
12604 }
12605
12606 void
12607 RetractMoveEvent()
12608 {
12609     switch (gameMode) {
12610       case MachinePlaysWhite:
12611       case MachinePlaysBlack:
12612         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12613             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12614             return;
12615         }
12616         if (forwardMostMove < 2) return;
12617         currentMove = forwardMostMove = forwardMostMove - 2;
12618         whiteTimeRemaining = timeRemaining[0][currentMove];
12619         blackTimeRemaining = timeRemaining[1][currentMove];
12620         DisplayBothClocks();
12621         DisplayMove(currentMove - 1);
12622         ClearHighlights();/*!! could figure this out*/
12623         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12624         SendToProgram("remove\n", &first);
12625         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12626         break;
12627
12628       case BeginningOfGame:
12629       default:
12630         break;
12631
12632       case IcsPlayingWhite:
12633       case IcsPlayingBlack:
12634         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12635             SendToICS(ics_prefix);
12636             SendToICS("takeback 2\n");
12637         } else {
12638             SendToICS(ics_prefix);
12639             SendToICS("takeback 1\n");
12640         }
12641         break;
12642     }
12643 }
12644
12645 void
12646 MoveNowEvent()
12647 {
12648     ChessProgramState *cps;
12649
12650     switch (gameMode) {
12651       case MachinePlaysWhite:
12652         if (!WhiteOnMove(forwardMostMove)) {
12653             DisplayError(_("It is your turn"), 0);
12654             return;
12655         }
12656         cps = &first;
12657         break;
12658       case MachinePlaysBlack:
12659         if (WhiteOnMove(forwardMostMove)) {
12660             DisplayError(_("It is your turn"), 0);
12661             return;
12662         }
12663         cps = &first;
12664         break;
12665       case TwoMachinesPlay:
12666         if (WhiteOnMove(forwardMostMove) ==
12667             (first.twoMachinesColor[0] == 'w')) {
12668             cps = &first;
12669         } else {
12670             cps = &second;
12671         }
12672         break;
12673       case BeginningOfGame:
12674       default:
12675         return;
12676     }
12677     SendToProgram("?\n", cps);
12678 }
12679
12680 void
12681 TruncateGameEvent()
12682 {
12683     EditGameEvent();
12684     if (gameMode != EditGame) return;
12685     TruncateGame();
12686 }
12687
12688 void
12689 TruncateGame()
12690 {
12691     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12692     if (forwardMostMove > currentMove) {
12693         if (gameInfo.resultDetails != NULL) {
12694             free(gameInfo.resultDetails);
12695             gameInfo.resultDetails = NULL;
12696             gameInfo.result = GameUnfinished;
12697         }
12698         forwardMostMove = currentMove;
12699         HistorySet(parseList, backwardMostMove, forwardMostMove,
12700                    currentMove-1);
12701     }
12702 }
12703
12704 void
12705 HintEvent()
12706 {
12707     if (appData.noChessProgram) return;
12708     switch (gameMode) {
12709       case MachinePlaysWhite:
12710         if (WhiteOnMove(forwardMostMove)) {
12711             DisplayError(_("Wait until your turn"), 0);
12712             return;
12713         }
12714         break;
12715       case BeginningOfGame:
12716       case MachinePlaysBlack:
12717         if (!WhiteOnMove(forwardMostMove)) {
12718             DisplayError(_("Wait until your turn"), 0);
12719             return;
12720         }
12721         break;
12722       default:
12723         DisplayError(_("No hint available"), 0);
12724         return;
12725     }
12726     SendToProgram("hint\n", &first);
12727     hintRequested = TRUE;
12728 }
12729
12730 void
12731 BookEvent()
12732 {
12733     if (appData.noChessProgram) return;
12734     switch (gameMode) {
12735       case MachinePlaysWhite:
12736         if (WhiteOnMove(forwardMostMove)) {
12737             DisplayError(_("Wait until your turn"), 0);
12738             return;
12739         }
12740         break;
12741       case BeginningOfGame:
12742       case MachinePlaysBlack:
12743         if (!WhiteOnMove(forwardMostMove)) {
12744             DisplayError(_("Wait until your turn"), 0);
12745             return;
12746         }
12747         break;
12748       case EditPosition:
12749         EditPositionDone(TRUE);
12750         break;
12751       case TwoMachinesPlay:
12752         return;
12753       default:
12754         break;
12755     }
12756     SendToProgram("bk\n", &first);
12757     bookOutput[0] = NULLCHAR;
12758     bookRequested = TRUE;
12759 }
12760
12761 void
12762 AboutGameEvent()
12763 {
12764     char *tags = PGNTags(&gameInfo);
12765     TagsPopUp(tags, CmailMsg());
12766     free(tags);
12767 }
12768
12769 /* end button procedures */
12770
12771 void
12772 PrintPosition(fp, move)
12773      FILE *fp;
12774      int move;
12775 {
12776     int i, j;
12777     
12778     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12779         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12780             char c = PieceToChar(boards[move][i][j]);
12781             fputc(c == 'x' ? '.' : c, fp);
12782             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12783         }
12784     }
12785     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12786       fprintf(fp, "white to play\n");
12787     else
12788       fprintf(fp, "black to play\n");
12789 }
12790
12791 void
12792 PrintOpponents(fp)
12793      FILE *fp;
12794 {
12795     if (gameInfo.white != NULL) {
12796         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12797     } else {
12798         fprintf(fp, "\n");
12799     }
12800 }
12801
12802 /* Find last component of program's own name, using some heuristics */
12803 void
12804 TidyProgramName(prog, host, buf)
12805      char *prog, *host, buf[MSG_SIZ];
12806 {
12807     char *p, *q;
12808     int local = (strcmp(host, "localhost") == 0);
12809     while (!local && (p = strchr(prog, ';')) != NULL) {
12810         p++;
12811         while (*p == ' ') p++;
12812         prog = p;
12813     }
12814     if (*prog == '"' || *prog == '\'') {
12815         q = strchr(prog + 1, *prog);
12816     } else {
12817         q = strchr(prog, ' ');
12818     }
12819     if (q == NULL) q = prog + strlen(prog);
12820     p = q;
12821     while (p >= prog && *p != '/' && *p != '\\') p--;
12822     p++;
12823     if(p == prog && *p == '"') p++;
12824     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12825     memcpy(buf, p, q - p);
12826     buf[q - p] = NULLCHAR;
12827     if (!local) {
12828         strcat(buf, "@");
12829         strcat(buf, host);
12830     }
12831 }
12832
12833 char *
12834 TimeControlTagValue()
12835 {
12836     char buf[MSG_SIZ];
12837     if (!appData.clockMode) {
12838         strcpy(buf, "-");
12839     } else if (movesPerSession > 0) {
12840         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12841     } else if (timeIncrement == 0) {
12842         sprintf(buf, "%ld", timeControl/1000);
12843     } else {
12844         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12845     }
12846     return StrSave(buf);
12847 }
12848
12849 void
12850 SetGameInfo()
12851 {
12852     /* This routine is used only for certain modes */
12853     VariantClass v = gameInfo.variant;
12854     ChessMove r = GameUnfinished;
12855     char *p = NULL;
12856
12857     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12858         r = gameInfo.result; 
12859         p = gameInfo.resultDetails; 
12860         gameInfo.resultDetails = NULL;
12861     }
12862     ClearGameInfo(&gameInfo);
12863     gameInfo.variant = v;
12864
12865     switch (gameMode) {
12866       case MachinePlaysWhite:
12867         gameInfo.event = StrSave( appData.pgnEventHeader );
12868         gameInfo.site = StrSave(HostName());
12869         gameInfo.date = PGNDate();
12870         gameInfo.round = StrSave("-");
12871         gameInfo.white = StrSave(first.tidy);
12872         gameInfo.black = StrSave(UserName());
12873         gameInfo.timeControl = TimeControlTagValue();
12874         break;
12875
12876       case MachinePlaysBlack:
12877         gameInfo.event = StrSave( appData.pgnEventHeader );
12878         gameInfo.site = StrSave(HostName());
12879         gameInfo.date = PGNDate();
12880         gameInfo.round = StrSave("-");
12881         gameInfo.white = StrSave(UserName());
12882         gameInfo.black = StrSave(first.tidy);
12883         gameInfo.timeControl = TimeControlTagValue();
12884         break;
12885
12886       case TwoMachinesPlay:
12887         gameInfo.event = StrSave( appData.pgnEventHeader );
12888         gameInfo.site = StrSave(HostName());
12889         gameInfo.date = PGNDate();
12890         if (matchGame > 0) {
12891             char buf[MSG_SIZ];
12892             sprintf(buf, "%d", matchGame);
12893             gameInfo.round = StrSave(buf);
12894         } else {
12895             gameInfo.round = StrSave("-");
12896         }
12897         if (first.twoMachinesColor[0] == 'w') {
12898             gameInfo.white = StrSave(first.tidy);
12899             gameInfo.black = StrSave(second.tidy);
12900         } else {
12901             gameInfo.white = StrSave(second.tidy);
12902             gameInfo.black = StrSave(first.tidy);
12903         }
12904         gameInfo.timeControl = TimeControlTagValue();
12905         break;
12906
12907       case EditGame:
12908         gameInfo.event = StrSave("Edited game");
12909         gameInfo.site = StrSave(HostName());
12910         gameInfo.date = PGNDate();
12911         gameInfo.round = StrSave("-");
12912         gameInfo.white = StrSave("-");
12913         gameInfo.black = StrSave("-");
12914         gameInfo.result = r;
12915         gameInfo.resultDetails = p;
12916         break;
12917
12918       case EditPosition:
12919         gameInfo.event = StrSave("Edited position");
12920         gameInfo.site = StrSave(HostName());
12921         gameInfo.date = PGNDate();
12922         gameInfo.round = StrSave("-");
12923         gameInfo.white = StrSave("-");
12924         gameInfo.black = StrSave("-");
12925         break;
12926
12927       case IcsPlayingWhite:
12928       case IcsPlayingBlack:
12929       case IcsObserving:
12930       case IcsExamining:
12931         break;
12932
12933       case PlayFromGameFile:
12934         gameInfo.event = StrSave("Game from non-PGN file");
12935         gameInfo.site = StrSave(HostName());
12936         gameInfo.date = PGNDate();
12937         gameInfo.round = StrSave("-");
12938         gameInfo.white = StrSave("?");
12939         gameInfo.black = StrSave("?");
12940         break;
12941
12942       default:
12943         break;
12944     }
12945 }
12946
12947 void
12948 ReplaceComment(index, text)
12949      int index;
12950      char *text;
12951 {
12952     int len;
12953
12954     while (*text == '\n') text++;
12955     len = strlen(text);
12956     while (len > 0 && text[len - 1] == '\n') len--;
12957
12958     if (commentList[index] != NULL)
12959       free(commentList[index]);
12960
12961     if (len == 0) {
12962         commentList[index] = NULL;
12963         return;
12964     }
12965   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12966       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12967       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12968     commentList[index] = (char *) malloc(len + 2);
12969     strncpy(commentList[index], text, len);
12970     commentList[index][len] = '\n';
12971     commentList[index][len + 1] = NULLCHAR;
12972   } else { 
12973     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12974     char *p;
12975     commentList[index] = (char *) malloc(len + 6);
12976     strcpy(commentList[index], "{\n");
12977     strncpy(commentList[index]+2, text, len);
12978     commentList[index][len+2] = NULLCHAR;
12979     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12980     strcat(commentList[index], "\n}\n");
12981   }
12982 }
12983
12984 void
12985 CrushCRs(text)
12986      char *text;
12987 {
12988   char *p = text;
12989   char *q = text;
12990   char ch;
12991
12992   do {
12993     ch = *p++;
12994     if (ch == '\r') continue;
12995     *q++ = ch;
12996   } while (ch != '\0');
12997 }
12998
12999 void
13000 AppendComment(index, text, addBraces)
13001      int index;
13002      char *text;
13003      Boolean addBraces; // [HGM] braces: tells if we should add {}
13004 {
13005     int oldlen, len;
13006     char *old;
13007
13008 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13009     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13010
13011     CrushCRs(text);
13012     while (*text == '\n') text++;
13013     len = strlen(text);
13014     while (len > 0 && text[len - 1] == '\n') len--;
13015
13016     if (len == 0) return;
13017
13018     if (commentList[index] != NULL) {
13019         old = commentList[index];
13020         oldlen = strlen(old);
13021         while(commentList[index][oldlen-1] ==  '\n')
13022           commentList[index][--oldlen] = NULLCHAR;
13023         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13024         strcpy(commentList[index], old);
13025         free(old);
13026         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13027         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13028           if(addBraces) addBraces = FALSE; else { text++; len--; }
13029           while (*text == '\n') { text++; len--; }
13030           commentList[index][--oldlen] = NULLCHAR;
13031       }
13032         if(addBraces) strcat(commentList[index], "\n{\n");
13033         else          strcat(commentList[index], "\n");
13034         strcat(commentList[index], text);
13035         if(addBraces) strcat(commentList[index], "\n}\n");
13036         else          strcat(commentList[index], "\n");
13037     } else {
13038         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13039         if(addBraces)
13040              strcpy(commentList[index], "{\n");
13041         else commentList[index][0] = NULLCHAR;
13042         strcat(commentList[index], text);
13043         strcat(commentList[index], "\n");
13044         if(addBraces) strcat(commentList[index], "}\n");
13045     }
13046 }
13047
13048 static char * FindStr( char * text, char * sub_text )
13049 {
13050     char * result = strstr( text, sub_text );
13051
13052     if( result != NULL ) {
13053         result += strlen( sub_text );
13054     }
13055
13056     return result;
13057 }
13058
13059 /* [AS] Try to extract PV info from PGN comment */
13060 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13061 char *GetInfoFromComment( int index, char * text )
13062 {
13063     char * sep = text;
13064
13065     if( text != NULL && index > 0 ) {
13066         int score = 0;
13067         int depth = 0;
13068         int time = -1, sec = 0, deci;
13069         char * s_eval = FindStr( text, "[%eval " );
13070         char * s_emt = FindStr( text, "[%emt " );
13071
13072         if( s_eval != NULL || s_emt != NULL ) {
13073             /* New style */
13074             char delim;
13075
13076             if( s_eval != NULL ) {
13077                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13078                     return text;
13079                 }
13080
13081                 if( delim != ']' ) {
13082                     return text;
13083                 }
13084             }
13085
13086             if( s_emt != NULL ) {
13087             }
13088                 return text;
13089         }
13090         else {
13091             /* We expect something like: [+|-]nnn.nn/dd */
13092             int score_lo = 0;
13093
13094             if(*text != '{') return text; // [HGM] braces: must be normal comment
13095
13096             sep = strchr( text, '/' );
13097             if( sep == NULL || sep < (text+4) ) {
13098                 return text;
13099             }
13100
13101             time = -1; sec = -1; deci = -1;
13102             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13103                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13104                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13105                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13106                 return text;
13107             }
13108
13109             if( score_lo < 0 || score_lo >= 100 ) {
13110                 return text;
13111             }
13112
13113             if(sec >= 0) time = 600*time + 10*sec; else
13114             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13115
13116             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13117
13118             /* [HGM] PV time: now locate end of PV info */
13119             while( *++sep >= '0' && *sep <= '9'); // strip depth
13120             if(time >= 0)
13121             while( *++sep >= '0' && *sep <= '9'); // strip time
13122             if(sec >= 0)
13123             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13124             if(deci >= 0)
13125             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13126             while(*sep == ' ') sep++;
13127         }
13128
13129         if( depth <= 0 ) {
13130             return text;
13131         }
13132
13133         if( time < 0 ) {
13134             time = -1;
13135         }
13136
13137         pvInfoList[index-1].depth = depth;
13138         pvInfoList[index-1].score = score;
13139         pvInfoList[index-1].time  = 10*time; // centi-sec
13140         if(*sep == '}') *sep = 0; else *--sep = '{';
13141     }
13142     return sep;
13143 }
13144
13145 void
13146 SendToProgram(message, cps)
13147      char *message;
13148      ChessProgramState *cps;
13149 {
13150     int count, outCount, error;
13151     char buf[MSG_SIZ];
13152
13153     if (cps->pr == NULL) return;
13154     Attention(cps);
13155     
13156     if (appData.debugMode) {
13157         TimeMark now;
13158         GetTimeMark(&now);
13159         fprintf(debugFP, "%ld >%-6s: %s", 
13160                 SubtractTimeMarks(&now, &programStartTime),
13161                 cps->which, message);
13162     }
13163     
13164     count = strlen(message);
13165     outCount = OutputToProcess(cps->pr, message, count, &error);
13166     if (outCount < count && !exiting 
13167                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13168         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13169         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13170             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13171                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13172                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13173             } else {
13174                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13175             }
13176             gameInfo.resultDetails = StrSave(buf);
13177         }
13178         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13179     }
13180 }
13181
13182 void
13183 ReceiveFromProgram(isr, closure, message, count, error)
13184      InputSourceRef isr;
13185      VOIDSTAR closure;
13186      char *message;
13187      int count;
13188      int error;
13189 {
13190     char *end_str;
13191     char buf[MSG_SIZ];
13192     ChessProgramState *cps = (ChessProgramState *)closure;
13193
13194     if (isr != cps->isr) return; /* Killed intentionally */
13195     if (count <= 0) {
13196         if (count == 0) {
13197             sprintf(buf,
13198                     _("Error: %s chess program (%s) exited unexpectedly"),
13199                     cps->which, cps->program);
13200         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13201                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13202                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13203                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13204                 } else {
13205                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13206                 }
13207                 gameInfo.resultDetails = StrSave(buf);
13208             }
13209             RemoveInputSource(cps->isr);
13210             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13211         } else {
13212             sprintf(buf,
13213                     _("Error reading from %s chess program (%s)"),
13214                     cps->which, cps->program);
13215             RemoveInputSource(cps->isr);
13216
13217             /* [AS] Program is misbehaving badly... kill it */
13218             if( count == -2 ) {
13219                 DestroyChildProcess( cps->pr, 9 );
13220                 cps->pr = NoProc;
13221             }
13222
13223             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13224         }
13225         return;
13226     }
13227     
13228     if ((end_str = strchr(message, '\r')) != NULL)
13229       *end_str = NULLCHAR;
13230     if ((end_str = strchr(message, '\n')) != NULL)
13231       *end_str = NULLCHAR;
13232     
13233     if (appData.debugMode) {
13234         TimeMark now; int print = 1;
13235         char *quote = ""; char c; int i;
13236
13237         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13238                 char start = message[0];
13239                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13240                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13241                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13242                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13243                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13244                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13245                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13246                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13247                         { quote = "# "; print = (appData.engineComments == 2); }
13248                 message[0] = start; // restore original message
13249         }
13250         if(print) {
13251                 GetTimeMark(&now);
13252                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13253                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13254                         quote,
13255                         message);
13256         }
13257     }
13258
13259     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13260     if (appData.icsEngineAnalyze) {
13261         if (strstr(message, "whisper") != NULL ||
13262              strstr(message, "kibitz") != NULL || 
13263             strstr(message, "tellics") != NULL) return;
13264     }
13265
13266     HandleMachineMove(message, cps);
13267 }
13268
13269
13270 void
13271 SendTimeControl(cps, mps, tc, inc, sd, st)
13272      ChessProgramState *cps;
13273      int mps, inc, sd, st;
13274      long tc;
13275 {
13276     char buf[MSG_SIZ];
13277     int seconds;
13278
13279     if( timeControl_2 > 0 ) {
13280         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13281             tc = timeControl_2;
13282         }
13283     }
13284     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13285     inc /= cps->timeOdds;
13286     st  /= cps->timeOdds;
13287
13288     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13289
13290     if (st > 0) {
13291       /* Set exact time per move, normally using st command */
13292       if (cps->stKludge) {
13293         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13294         seconds = st % 60;
13295         if (seconds == 0) {
13296           sprintf(buf, "level 1 %d\n", st/60);
13297         } else {
13298           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13299         }
13300       } else {
13301         sprintf(buf, "st %d\n", st);
13302       }
13303     } else {
13304       /* Set conventional or incremental time control, using level command */
13305       if (seconds == 0) {
13306         /* Note old gnuchess bug -- minutes:seconds used to not work.
13307            Fixed in later versions, but still avoid :seconds
13308            when seconds is 0. */
13309         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13310       } else {
13311         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13312                 seconds, inc/1000);
13313       }
13314     }
13315     SendToProgram(buf, cps);
13316
13317     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13318     /* Orthogonally, limit search to given depth */
13319     if (sd > 0) {
13320       if (cps->sdKludge) {
13321         sprintf(buf, "depth\n%d\n", sd);
13322       } else {
13323         sprintf(buf, "sd %d\n", sd);
13324       }
13325       SendToProgram(buf, cps);
13326     }
13327
13328     if(cps->nps > 0) { /* [HGM] nps */
13329         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13330         else {
13331                 sprintf(buf, "nps %d\n", cps->nps);
13332               SendToProgram(buf, cps);
13333         }
13334     }
13335 }
13336
13337 ChessProgramState *WhitePlayer()
13338 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13339 {
13340     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13341        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13342         return &second;
13343     return &first;
13344 }
13345
13346 void
13347 SendTimeRemaining(cps, machineWhite)
13348      ChessProgramState *cps;
13349      int /*boolean*/ machineWhite;
13350 {
13351     char message[MSG_SIZ];
13352     long time, otime;
13353
13354     /* Note: this routine must be called when the clocks are stopped
13355        or when they have *just* been set or switched; otherwise
13356        it will be off by the time since the current tick started.
13357     */
13358     if (machineWhite) {
13359         time = whiteTimeRemaining / 10;
13360         otime = blackTimeRemaining / 10;
13361     } else {
13362         time = blackTimeRemaining / 10;
13363         otime = whiteTimeRemaining / 10;
13364     }
13365     /* [HGM] translate opponent's time by time-odds factor */
13366     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13367     if (appData.debugMode) {
13368         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13369     }
13370
13371     if (time <= 0) time = 1;
13372     if (otime <= 0) otime = 1;
13373     
13374     sprintf(message, "time %ld\n", time);
13375     SendToProgram(message, cps);
13376
13377     sprintf(message, "otim %ld\n", otime);
13378     SendToProgram(message, cps);
13379 }
13380
13381 int
13382 BoolFeature(p, name, loc, cps)
13383      char **p;
13384      char *name;
13385      int *loc;
13386      ChessProgramState *cps;
13387 {
13388   char buf[MSG_SIZ];
13389   int len = strlen(name);
13390   int val;
13391   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13392     (*p) += len + 1;
13393     sscanf(*p, "%d", &val);
13394     *loc = (val != 0);
13395     while (**p && **p != ' ') (*p)++;
13396     sprintf(buf, "accepted %s\n", name);
13397     SendToProgram(buf, cps);
13398     return TRUE;
13399   }
13400   return FALSE;
13401 }
13402
13403 int
13404 IntFeature(p, name, loc, cps)
13405      char **p;
13406      char *name;
13407      int *loc;
13408      ChessProgramState *cps;
13409 {
13410   char buf[MSG_SIZ];
13411   int len = strlen(name);
13412   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13413     (*p) += len + 1;
13414     sscanf(*p, "%d", loc);
13415     while (**p && **p != ' ') (*p)++;
13416     sprintf(buf, "accepted %s\n", name);
13417     SendToProgram(buf, cps);
13418     return TRUE;
13419   }
13420   return FALSE;
13421 }
13422
13423 int
13424 StringFeature(p, name, loc, cps)
13425      char **p;
13426      char *name;
13427      char loc[];
13428      ChessProgramState *cps;
13429 {
13430   char buf[MSG_SIZ];
13431   int len = strlen(name);
13432   if (strncmp((*p), name, len) == 0
13433       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13434     (*p) += len + 2;
13435     sscanf(*p, "%[^\"]", loc);
13436     while (**p && **p != '\"') (*p)++;
13437     if (**p == '\"') (*p)++;
13438     sprintf(buf, "accepted %s\n", name);
13439     SendToProgram(buf, cps);
13440     return TRUE;
13441   }
13442   return FALSE;
13443 }
13444
13445 int 
13446 ParseOption(Option *opt, ChessProgramState *cps)
13447 // [HGM] options: process the string that defines an engine option, and determine
13448 // name, type, default value, and allowed value range
13449 {
13450         char *p, *q, buf[MSG_SIZ];
13451         int n, min = (-1)<<31, max = 1<<31, def;
13452
13453         if(p = strstr(opt->name, " -spin ")) {
13454             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13455             if(max < min) max = min; // enforce consistency
13456             if(def < min) def = min;
13457             if(def > max) def = max;
13458             opt->value = def;
13459             opt->min = min;
13460             opt->max = max;
13461             opt->type = Spin;
13462         } else if((p = strstr(opt->name, " -slider "))) {
13463             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13464             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13465             if(max < min) max = min; // enforce consistency
13466             if(def < min) def = min;
13467             if(def > max) def = max;
13468             opt->value = def;
13469             opt->min = min;
13470             opt->max = max;
13471             opt->type = Spin; // Slider;
13472         } else if((p = strstr(opt->name, " -string "))) {
13473             opt->textValue = p+9;
13474             opt->type = TextBox;
13475         } else if((p = strstr(opt->name, " -file "))) {
13476             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13477             opt->textValue = p+7;
13478             opt->type = TextBox; // FileName;
13479         } else if((p = strstr(opt->name, " -path "))) {
13480             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13481             opt->textValue = p+7;
13482             opt->type = TextBox; // PathName;
13483         } else if(p = strstr(opt->name, " -check ")) {
13484             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13485             opt->value = (def != 0);
13486             opt->type = CheckBox;
13487         } else if(p = strstr(opt->name, " -combo ")) {
13488             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13489             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13490             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13491             opt->value = n = 0;
13492             while(q = StrStr(q, " /// ")) {
13493                 n++; *q = 0;    // count choices, and null-terminate each of them
13494                 q += 5;
13495                 if(*q == '*') { // remember default, which is marked with * prefix
13496                     q++;
13497                     opt->value = n;
13498                 }
13499                 cps->comboList[cps->comboCnt++] = q;
13500             }
13501             cps->comboList[cps->comboCnt++] = NULL;
13502             opt->max = n + 1;
13503             opt->type = ComboBox;
13504         } else if(p = strstr(opt->name, " -button")) {
13505             opt->type = Button;
13506         } else if(p = strstr(opt->name, " -save")) {
13507             opt->type = SaveButton;
13508         } else return FALSE;
13509         *p = 0; // terminate option name
13510         // now look if the command-line options define a setting for this engine option.
13511         if(cps->optionSettings && cps->optionSettings[0])
13512             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13513         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13514                 sprintf(buf, "option %s", p);
13515                 if(p = strstr(buf, ",")) *p = 0;
13516                 strcat(buf, "\n");
13517                 SendToProgram(buf, cps);
13518         }
13519         return TRUE;
13520 }
13521
13522 void
13523 FeatureDone(cps, val)
13524      ChessProgramState* cps;
13525      int val;
13526 {
13527   DelayedEventCallback cb = GetDelayedEvent();
13528   if ((cb == InitBackEnd3 && cps == &first) ||
13529       (cb == TwoMachinesEventIfReady && cps == &second)) {
13530     CancelDelayedEvent();
13531     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13532   }
13533   cps->initDone = val;
13534 }
13535
13536 /* Parse feature command from engine */
13537 void
13538 ParseFeatures(args, cps)
13539      char* args;
13540      ChessProgramState *cps;  
13541 {
13542   char *p = args;
13543   char *q;
13544   int val;
13545   char buf[MSG_SIZ];
13546
13547   for (;;) {
13548     while (*p == ' ') p++;
13549     if (*p == NULLCHAR) return;
13550
13551     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13552     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13553     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13554     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13555     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13556     if (BoolFeature(&p, "reuse", &val, cps)) {
13557       /* Engine can disable reuse, but can't enable it if user said no */
13558       if (!val) cps->reuse = FALSE;
13559       continue;
13560     }
13561     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13562     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13563       if (gameMode == TwoMachinesPlay) {
13564         DisplayTwoMachinesTitle();
13565       } else {
13566         DisplayTitle("");
13567       }
13568       continue;
13569     }
13570     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13571     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13572     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13573     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13574     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13575     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13576     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13577     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13578     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13579     if (IntFeature(&p, "done", &val, cps)) {
13580       FeatureDone(cps, val);
13581       continue;
13582     }
13583     /* Added by Tord: */
13584     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13585     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13586     /* End of additions by Tord */
13587
13588     /* [HGM] added features: */
13589     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13590     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13591     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13592     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13593     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13594     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13595     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13596         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13597             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13598             SendToProgram(buf, cps);
13599             continue;
13600         }
13601         if(cps->nrOptions >= MAX_OPTIONS) {
13602             cps->nrOptions--;
13603             sprintf(buf, "%s engine has too many options\n", cps->which);
13604             DisplayError(buf, 0);
13605         }
13606         continue;
13607     }
13608     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13609     /* End of additions by HGM */
13610
13611     /* unknown feature: complain and skip */
13612     q = p;
13613     while (*q && *q != '=') q++;
13614     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13615     SendToProgram(buf, cps);
13616     p = q;
13617     if (*p == '=') {
13618       p++;
13619       if (*p == '\"') {
13620         p++;
13621         while (*p && *p != '\"') p++;
13622         if (*p == '\"') p++;
13623       } else {
13624         while (*p && *p != ' ') p++;
13625       }
13626     }
13627   }
13628
13629 }
13630
13631 void
13632 PeriodicUpdatesEvent(newState)
13633      int newState;
13634 {
13635     if (newState == appData.periodicUpdates)
13636       return;
13637
13638     appData.periodicUpdates=newState;
13639
13640     /* Display type changes, so update it now */
13641 //    DisplayAnalysis();
13642
13643     /* Get the ball rolling again... */
13644     if (newState) {
13645         AnalysisPeriodicEvent(1);
13646         StartAnalysisClock();
13647     }
13648 }
13649
13650 void
13651 PonderNextMoveEvent(newState)
13652      int newState;
13653 {
13654     if (newState == appData.ponderNextMove) return;
13655     if (gameMode == EditPosition) EditPositionDone(TRUE);
13656     if (newState) {
13657         SendToProgram("hard\n", &first);
13658         if (gameMode == TwoMachinesPlay) {
13659             SendToProgram("hard\n", &second);
13660         }
13661     } else {
13662         SendToProgram("easy\n", &first);
13663         thinkOutput[0] = NULLCHAR;
13664         if (gameMode == TwoMachinesPlay) {
13665             SendToProgram("easy\n", &second);
13666         }
13667     }
13668     appData.ponderNextMove = newState;
13669 }
13670
13671 void
13672 NewSettingEvent(option, command, value)
13673      char *command;
13674      int option, value;
13675 {
13676     char buf[MSG_SIZ];
13677
13678     if (gameMode == EditPosition) EditPositionDone(TRUE);
13679     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13680     SendToProgram(buf, &first);
13681     if (gameMode == TwoMachinesPlay) {
13682         SendToProgram(buf, &second);
13683     }
13684 }
13685
13686 void
13687 ShowThinkingEvent()
13688 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13689 {
13690     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13691     int newState = appData.showThinking
13692         // [HGM] thinking: other features now need thinking output as well
13693         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13694     
13695     if (oldState == newState) return;
13696     oldState = newState;
13697     if (gameMode == EditPosition) EditPositionDone(TRUE);
13698     if (oldState) {
13699         SendToProgram("post\n", &first);
13700         if (gameMode == TwoMachinesPlay) {
13701             SendToProgram("post\n", &second);
13702         }
13703     } else {
13704         SendToProgram("nopost\n", &first);
13705         thinkOutput[0] = NULLCHAR;
13706         if (gameMode == TwoMachinesPlay) {
13707             SendToProgram("nopost\n", &second);
13708         }
13709     }
13710 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13711 }
13712
13713 void
13714 AskQuestionEvent(title, question, replyPrefix, which)
13715      char *title; char *question; char *replyPrefix; char *which;
13716 {
13717   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13718   if (pr == NoProc) return;
13719   AskQuestion(title, question, replyPrefix, pr);
13720 }
13721
13722 void
13723 DisplayMove(moveNumber)
13724      int moveNumber;
13725 {
13726     char message[MSG_SIZ];
13727     char res[MSG_SIZ];
13728     char cpThinkOutput[MSG_SIZ];
13729
13730     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13731     
13732     if (moveNumber == forwardMostMove - 1 || 
13733         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13734
13735         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13736
13737         if (strchr(cpThinkOutput, '\n')) {
13738             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13739         }
13740     } else {
13741         *cpThinkOutput = NULLCHAR;
13742     }
13743
13744     /* [AS] Hide thinking from human user */
13745     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13746         *cpThinkOutput = NULLCHAR;
13747         if( thinkOutput[0] != NULLCHAR ) {
13748             int i;
13749
13750             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13751                 cpThinkOutput[i] = '.';
13752             }
13753             cpThinkOutput[i] = NULLCHAR;
13754             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13755         }
13756     }
13757
13758     if (moveNumber == forwardMostMove - 1 &&
13759         gameInfo.resultDetails != NULL) {
13760         if (gameInfo.resultDetails[0] == NULLCHAR) {
13761             sprintf(res, " %s", PGNResult(gameInfo.result));
13762         } else {
13763             sprintf(res, " {%s} %s",
13764                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13765         }
13766     } else {
13767         res[0] = NULLCHAR;
13768     }
13769
13770     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13771         DisplayMessage(res, cpThinkOutput);
13772     } else {
13773         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13774                 WhiteOnMove(moveNumber) ? " " : ".. ",
13775                 parseList[moveNumber], res);
13776         DisplayMessage(message, cpThinkOutput);
13777     }
13778 }
13779
13780 void
13781 DisplayComment(moveNumber, text)
13782      int moveNumber;
13783      char *text;
13784 {
13785     char title[MSG_SIZ];
13786     char buf[8000]; // comment can be long!
13787     int score, depth;
13788     
13789     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13790       strcpy(title, "Comment");
13791     } else {
13792       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13793               WhiteOnMove(moveNumber) ? " " : ".. ",
13794               parseList[moveNumber]);
13795     }
13796     // [HGM] PV info: display PV info together with (or as) comment
13797     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13798       if(text == NULL) text = "";                                           
13799       score = pvInfoList[moveNumber].score;
13800       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13801               depth, (pvInfoList[moveNumber].time+50)/100, text);
13802       text = buf;
13803     }
13804     if (text != NULL && (appData.autoDisplayComment || commentUp))
13805         CommentPopUp(title, text);
13806 }
13807
13808 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13809  * might be busy thinking or pondering.  It can be omitted if your
13810  * gnuchess is configured to stop thinking immediately on any user
13811  * input.  However, that gnuchess feature depends on the FIONREAD
13812  * ioctl, which does not work properly on some flavors of Unix.
13813  */
13814 void
13815 Attention(cps)
13816      ChessProgramState *cps;
13817 {
13818 #if ATTENTION
13819     if (!cps->useSigint) return;
13820     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13821     switch (gameMode) {
13822       case MachinePlaysWhite:
13823       case MachinePlaysBlack:
13824       case TwoMachinesPlay:
13825       case IcsPlayingWhite:
13826       case IcsPlayingBlack:
13827       case AnalyzeMode:
13828       case AnalyzeFile:
13829         /* Skip if we know it isn't thinking */
13830         if (!cps->maybeThinking) return;
13831         if (appData.debugMode)
13832           fprintf(debugFP, "Interrupting %s\n", cps->which);
13833         InterruptChildProcess(cps->pr);
13834         cps->maybeThinking = FALSE;
13835         break;
13836       default:
13837         break;
13838     }
13839 #endif /*ATTENTION*/
13840 }
13841
13842 int
13843 CheckFlags()
13844 {
13845     if (whiteTimeRemaining <= 0) {
13846         if (!whiteFlag) {
13847             whiteFlag = TRUE;
13848             if (appData.icsActive) {
13849                 if (appData.autoCallFlag &&
13850                     gameMode == IcsPlayingBlack && !blackFlag) {
13851                   SendToICS(ics_prefix);
13852                   SendToICS("flag\n");
13853                 }
13854             } else {
13855                 if (blackFlag) {
13856                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13857                 } else {
13858                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13859                     if (appData.autoCallFlag) {
13860                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13861                         return TRUE;
13862                     }
13863                 }
13864             }
13865         }
13866     }
13867     if (blackTimeRemaining <= 0) {
13868         if (!blackFlag) {
13869             blackFlag = TRUE;
13870             if (appData.icsActive) {
13871                 if (appData.autoCallFlag &&
13872                     gameMode == IcsPlayingWhite && !whiteFlag) {
13873                   SendToICS(ics_prefix);
13874                   SendToICS("flag\n");
13875                 }
13876             } else {
13877                 if (whiteFlag) {
13878                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13879                 } else {
13880                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13881                     if (appData.autoCallFlag) {
13882                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13883                         return TRUE;
13884                     }
13885                 }
13886             }
13887         }
13888     }
13889     return FALSE;
13890 }
13891
13892 void
13893 CheckTimeControl()
13894 {
13895     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13896         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13897
13898     /*
13899      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13900      */
13901     if ( !WhiteOnMove(forwardMostMove) )
13902         /* White made time control */
13903         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13904         /* [HGM] time odds: correct new time quota for time odds! */
13905                                             / WhitePlayer()->timeOdds;
13906       else
13907         /* Black made time control */
13908         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13909                                             / WhitePlayer()->other->timeOdds;
13910 }
13911
13912 void
13913 DisplayBothClocks()
13914 {
13915     int wom = gameMode == EditPosition ?
13916       !blackPlaysFirst : WhiteOnMove(currentMove);
13917     DisplayWhiteClock(whiteTimeRemaining, wom);
13918     DisplayBlackClock(blackTimeRemaining, !wom);
13919 }
13920
13921
13922 /* Timekeeping seems to be a portability nightmare.  I think everyone
13923    has ftime(), but I'm really not sure, so I'm including some ifdefs
13924    to use other calls if you don't.  Clocks will be less accurate if
13925    you have neither ftime nor gettimeofday.
13926 */
13927
13928 /* VS 2008 requires the #include outside of the function */
13929 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13930 #include <sys/timeb.h>
13931 #endif
13932
13933 /* Get the current time as a TimeMark */
13934 void
13935 GetTimeMark(tm)
13936      TimeMark *tm;
13937 {
13938 #if HAVE_GETTIMEOFDAY
13939
13940     struct timeval timeVal;
13941     struct timezone timeZone;
13942
13943     gettimeofday(&timeVal, &timeZone);
13944     tm->sec = (long) timeVal.tv_sec; 
13945     tm->ms = (int) (timeVal.tv_usec / 1000L);
13946
13947 #else /*!HAVE_GETTIMEOFDAY*/
13948 #if HAVE_FTIME
13949
13950 // include <sys/timeb.h> / moved to just above start of function
13951     struct timeb timeB;
13952
13953     ftime(&timeB);
13954     tm->sec = (long) timeB.time;
13955     tm->ms = (int) timeB.millitm;
13956
13957 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13958     tm->sec = (long) time(NULL);
13959     tm->ms = 0;
13960 #endif
13961 #endif
13962 }
13963
13964 /* Return the difference in milliseconds between two
13965    time marks.  We assume the difference will fit in a long!
13966 */
13967 long
13968 SubtractTimeMarks(tm2, tm1)
13969      TimeMark *tm2, *tm1;
13970 {
13971     return 1000L*(tm2->sec - tm1->sec) +
13972            (long) (tm2->ms - tm1->ms);
13973 }
13974
13975
13976 /*
13977  * Code to manage the game clocks.
13978  *
13979  * In tournament play, black starts the clock and then white makes a move.
13980  * We give the human user a slight advantage if he is playing white---the
13981  * clocks don't run until he makes his first move, so it takes zero time.
13982  * Also, we don't account for network lag, so we could get out of sync
13983  * with GNU Chess's clock -- but then, referees are always right.  
13984  */
13985
13986 static TimeMark tickStartTM;
13987 static long intendedTickLength;
13988
13989 long
13990 NextTickLength(timeRemaining)
13991      long timeRemaining;
13992 {
13993     long nominalTickLength, nextTickLength;
13994
13995     if (timeRemaining > 0L && timeRemaining <= 10000L)
13996       nominalTickLength = 100L;
13997     else
13998       nominalTickLength = 1000L;
13999     nextTickLength = timeRemaining % nominalTickLength;
14000     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14001
14002     return nextTickLength;
14003 }
14004
14005 /* Adjust clock one minute up or down */
14006 void
14007 AdjustClock(Boolean which, int dir)
14008 {
14009     if(which) blackTimeRemaining += 60000*dir;
14010     else      whiteTimeRemaining += 60000*dir;
14011     DisplayBothClocks();
14012 }
14013
14014 /* Stop clocks and reset to a fresh time control */
14015 void
14016 ResetClocks() 
14017 {
14018     (void) StopClockTimer();
14019     if (appData.icsActive) {
14020         whiteTimeRemaining = blackTimeRemaining = 0;
14021     } else if (searchTime) {
14022         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14023         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14024     } else { /* [HGM] correct new time quote for time odds */
14025         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14026         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14027     }
14028     if (whiteFlag || blackFlag) {
14029         DisplayTitle("");
14030         whiteFlag = blackFlag = FALSE;
14031     }
14032     DisplayBothClocks();
14033 }
14034
14035 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14036
14037 /* Decrement running clock by amount of time that has passed */
14038 void
14039 DecrementClocks()
14040 {
14041     long timeRemaining;
14042     long lastTickLength, fudge;
14043     TimeMark now;
14044
14045     if (!appData.clockMode) return;
14046     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14047         
14048     GetTimeMark(&now);
14049
14050     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14051
14052     /* Fudge if we woke up a little too soon */
14053     fudge = intendedTickLength - lastTickLength;
14054     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14055
14056     if (WhiteOnMove(forwardMostMove)) {
14057         if(whiteNPS >= 0) lastTickLength = 0;
14058         timeRemaining = whiteTimeRemaining -= lastTickLength;
14059         DisplayWhiteClock(whiteTimeRemaining - fudge,
14060                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14061     } else {
14062         if(blackNPS >= 0) lastTickLength = 0;
14063         timeRemaining = blackTimeRemaining -= lastTickLength;
14064         DisplayBlackClock(blackTimeRemaining - fudge,
14065                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14066     }
14067
14068     if (CheckFlags()) return;
14069         
14070     tickStartTM = now;
14071     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14072     StartClockTimer(intendedTickLength);
14073
14074     /* if the time remaining has fallen below the alarm threshold, sound the
14075      * alarm. if the alarm has sounded and (due to a takeback or time control
14076      * with increment) the time remaining has increased to a level above the
14077      * threshold, reset the alarm so it can sound again. 
14078      */
14079     
14080     if (appData.icsActive && appData.icsAlarm) {
14081
14082         /* make sure we are dealing with the user's clock */
14083         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14084                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14085            )) return;
14086
14087         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14088             alarmSounded = FALSE;
14089         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14090             PlayAlarmSound();
14091             alarmSounded = TRUE;
14092         }
14093     }
14094 }
14095
14096
14097 /* A player has just moved, so stop the previously running
14098    clock and (if in clock mode) start the other one.
14099    We redisplay both clocks in case we're in ICS mode, because
14100    ICS gives us an update to both clocks after every move.
14101    Note that this routine is called *after* forwardMostMove
14102    is updated, so the last fractional tick must be subtracted
14103    from the color that is *not* on move now.
14104 */
14105 void
14106 SwitchClocks(int newMoveNr)
14107 {
14108     long lastTickLength;
14109     TimeMark now;
14110     int flagged = FALSE;
14111
14112     GetTimeMark(&now);
14113
14114     if (StopClockTimer() && appData.clockMode) {
14115         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14116         if (!WhiteOnMove(forwardMostMove)) {
14117             if(blackNPS >= 0) lastTickLength = 0;
14118             blackTimeRemaining -= lastTickLength;
14119            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14120 //         if(pvInfoList[forwardMostMove-1].time == -1)
14121                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14122                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14123         } else {
14124            if(whiteNPS >= 0) lastTickLength = 0;
14125            whiteTimeRemaining -= lastTickLength;
14126            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14127 //         if(pvInfoList[forwardMostMove-1].time == -1)
14128                  pvInfoList[forwardMostMove-1].time = 
14129                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14130         }
14131         flagged = CheckFlags();
14132     }
14133     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14134     CheckTimeControl();
14135
14136     if (flagged || !appData.clockMode) return;
14137
14138     switch (gameMode) {
14139       case MachinePlaysBlack:
14140       case MachinePlaysWhite:
14141       case BeginningOfGame:
14142         if (pausing) return;
14143         break;
14144
14145       case EditGame:
14146       case PlayFromGameFile:
14147       case IcsExamining:
14148         return;
14149
14150       default:
14151         break;
14152     }
14153
14154     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14155         if(WhiteOnMove(forwardMostMove))
14156              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14157         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14158     }
14159
14160     tickStartTM = now;
14161     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14162       whiteTimeRemaining : blackTimeRemaining);
14163     StartClockTimer(intendedTickLength);
14164 }
14165         
14166
14167 /* Stop both clocks */
14168 void
14169 StopClocks()
14170 {       
14171     long lastTickLength;
14172     TimeMark now;
14173
14174     if (!StopClockTimer()) return;
14175     if (!appData.clockMode) return;
14176
14177     GetTimeMark(&now);
14178
14179     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14180     if (WhiteOnMove(forwardMostMove)) {
14181         if(whiteNPS >= 0) lastTickLength = 0;
14182         whiteTimeRemaining -= lastTickLength;
14183         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14184     } else {
14185         if(blackNPS >= 0) lastTickLength = 0;
14186         blackTimeRemaining -= lastTickLength;
14187         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14188     }
14189     CheckFlags();
14190 }
14191         
14192 /* Start clock of player on move.  Time may have been reset, so
14193    if clock is already running, stop and restart it. */
14194 void
14195 StartClocks()
14196 {
14197     (void) StopClockTimer(); /* in case it was running already */
14198     DisplayBothClocks();
14199     if (CheckFlags()) return;
14200
14201     if (!appData.clockMode) return;
14202     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14203
14204     GetTimeMark(&tickStartTM);
14205     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14206       whiteTimeRemaining : blackTimeRemaining);
14207
14208    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14209     whiteNPS = blackNPS = -1; 
14210     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14211        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14212         whiteNPS = first.nps;
14213     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14214        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14215         blackNPS = first.nps;
14216     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14217         whiteNPS = second.nps;
14218     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14219         blackNPS = second.nps;
14220     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14221
14222     StartClockTimer(intendedTickLength);
14223 }
14224
14225 char *
14226 TimeString(ms)
14227      long ms;
14228 {
14229     long second, minute, hour, day;
14230     char *sign = "";
14231     static char buf[32];
14232     
14233     if (ms > 0 && ms <= 9900) {
14234       /* convert milliseconds to tenths, rounding up */
14235       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14236
14237       sprintf(buf, " %03.1f ", tenths/10.0);
14238       return buf;
14239     }
14240
14241     /* convert milliseconds to seconds, rounding up */
14242     /* use floating point to avoid strangeness of integer division
14243        with negative dividends on many machines */
14244     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14245
14246     if (second < 0) {
14247         sign = "-";
14248         second = -second;
14249     }
14250     
14251     day = second / (60 * 60 * 24);
14252     second = second % (60 * 60 * 24);
14253     hour = second / (60 * 60);
14254     second = second % (60 * 60);
14255     minute = second / 60;
14256     second = second % 60;
14257     
14258     if (day > 0)
14259       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14260               sign, day, hour, minute, second);
14261     else if (hour > 0)
14262       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14263     else
14264       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14265     
14266     return buf;
14267 }
14268
14269
14270 /*
14271  * This is necessary because some C libraries aren't ANSI C compliant yet.
14272  */
14273 char *
14274 StrStr(string, match)
14275      char *string, *match;
14276 {
14277     int i, length;
14278     
14279     length = strlen(match);
14280     
14281     for (i = strlen(string) - length; i >= 0; i--, string++)
14282       if (!strncmp(match, string, length))
14283         return string;
14284     
14285     return NULL;
14286 }
14287
14288 char *
14289 StrCaseStr(string, match)
14290      char *string, *match;
14291 {
14292     int i, j, length;
14293     
14294     length = strlen(match);
14295     
14296     for (i = strlen(string) - length; i >= 0; i--, string++) {
14297         for (j = 0; j < length; j++) {
14298             if (ToLower(match[j]) != ToLower(string[j]))
14299               break;
14300         }
14301         if (j == length) return string;
14302     }
14303
14304     return NULL;
14305 }
14306
14307 #ifndef _amigados
14308 int
14309 StrCaseCmp(s1, s2)
14310      char *s1, *s2;
14311 {
14312     char c1, c2;
14313     
14314     for (;;) {
14315         c1 = ToLower(*s1++);
14316         c2 = ToLower(*s2++);
14317         if (c1 > c2) return 1;
14318         if (c1 < c2) return -1;
14319         if (c1 == NULLCHAR) return 0;
14320     }
14321 }
14322
14323
14324 int
14325 ToLower(c)
14326      int c;
14327 {
14328     return isupper(c) ? tolower(c) : c;
14329 }
14330
14331
14332 int
14333 ToUpper(c)
14334      int c;
14335 {
14336     return islower(c) ? toupper(c) : c;
14337 }
14338 #endif /* !_amigados    */
14339
14340 char *
14341 StrSave(s)
14342      char *s;
14343 {
14344     char *ret;
14345
14346     if ((ret = (char *) malloc(strlen(s) + 1))) {
14347         strcpy(ret, s);
14348     }
14349     return ret;
14350 }
14351
14352 char *
14353 StrSavePtr(s, savePtr)
14354      char *s, **savePtr;
14355 {
14356     if (*savePtr) {
14357         free(*savePtr);
14358     }
14359     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14360         strcpy(*savePtr, s);
14361     }
14362     return(*savePtr);
14363 }
14364
14365 char *
14366 PGNDate()
14367 {
14368     time_t clock;
14369     struct tm *tm;
14370     char buf[MSG_SIZ];
14371
14372     clock = time((time_t *)NULL);
14373     tm = localtime(&clock);
14374     sprintf(buf, "%04d.%02d.%02d",
14375             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14376     return StrSave(buf);
14377 }
14378
14379
14380 char *
14381 PositionToFEN(move, overrideCastling)
14382      int move;
14383      char *overrideCastling;
14384 {
14385     int i, j, fromX, fromY, toX, toY;
14386     int whiteToPlay;
14387     char buf[128];
14388     char *p, *q;
14389     int emptycount;
14390     ChessSquare piece;
14391
14392     whiteToPlay = (gameMode == EditPosition) ?
14393       !blackPlaysFirst : (move % 2 == 0);
14394     p = buf;
14395
14396     /* Piece placement data */
14397     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14398         emptycount = 0;
14399         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14400             if (boards[move][i][j] == EmptySquare) {
14401                 emptycount++;
14402             } else { ChessSquare piece = boards[move][i][j];
14403                 if (emptycount > 0) {
14404                     if(emptycount<10) /* [HGM] can be >= 10 */
14405                         *p++ = '0' + emptycount;
14406                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14407                     emptycount = 0;
14408                 }
14409                 if(PieceToChar(piece) == '+') {
14410                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14411                     *p++ = '+';
14412                     piece = (ChessSquare)(DEMOTED piece);
14413                 } 
14414                 *p++ = PieceToChar(piece);
14415                 if(p[-1] == '~') {
14416                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14417                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14418                     *p++ = '~';
14419                 }
14420             }
14421         }
14422         if (emptycount > 0) {
14423             if(emptycount<10) /* [HGM] can be >= 10 */
14424                 *p++ = '0' + emptycount;
14425             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14426             emptycount = 0;
14427         }
14428         *p++ = '/';
14429     }
14430     *(p - 1) = ' ';
14431
14432     /* [HGM] print Crazyhouse or Shogi holdings */
14433     if( gameInfo.holdingsWidth ) {
14434         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14435         q = p;
14436         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14437             piece = boards[move][i][BOARD_WIDTH-1];
14438             if( piece != EmptySquare )
14439               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14440                   *p++ = PieceToChar(piece);
14441         }
14442         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14443             piece = boards[move][BOARD_HEIGHT-i-1][0];
14444             if( piece != EmptySquare )
14445               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14446                   *p++ = PieceToChar(piece);
14447         }
14448
14449         if( q == p ) *p++ = '-';
14450         *p++ = ']';
14451         *p++ = ' ';
14452     }
14453
14454     /* Active color */
14455     *p++ = whiteToPlay ? 'w' : 'b';
14456     *p++ = ' ';
14457
14458   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14459     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14460   } else {
14461   if(nrCastlingRights) {
14462      q = p;
14463      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14464        /* [HGM] write directly from rights */
14465            if(boards[move][CASTLING][2] != NoRights &&
14466               boards[move][CASTLING][0] != NoRights   )
14467                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14468            if(boards[move][CASTLING][2] != NoRights &&
14469               boards[move][CASTLING][1] != NoRights   )
14470                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14471            if(boards[move][CASTLING][5] != NoRights &&
14472               boards[move][CASTLING][3] != NoRights   )
14473                 *p++ = boards[move][CASTLING][3] + AAA;
14474            if(boards[move][CASTLING][5] != NoRights &&
14475               boards[move][CASTLING][4] != NoRights   )
14476                 *p++ = boards[move][CASTLING][4] + AAA;
14477      } else {
14478
14479         /* [HGM] write true castling rights */
14480         if( nrCastlingRights == 6 ) {
14481             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14482                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14483             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14484                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14485             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14486                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14487             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14488                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14489         }
14490      }
14491      if (q == p) *p++ = '-'; /* No castling rights */
14492      *p++ = ' ';
14493   }
14494
14495   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14496      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14497     /* En passant target square */
14498     if (move > backwardMostMove) {
14499         fromX = moveList[move - 1][0] - AAA;
14500         fromY = moveList[move - 1][1] - ONE;
14501         toX = moveList[move - 1][2] - AAA;
14502         toY = moveList[move - 1][3] - ONE;
14503         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14504             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14505             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14506             fromX == toX) {
14507             /* 2-square pawn move just happened */
14508             *p++ = toX + AAA;
14509             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14510         } else {
14511             *p++ = '-';
14512         }
14513     } else if(move == backwardMostMove) {
14514         // [HGM] perhaps we should always do it like this, and forget the above?
14515         if((signed char)boards[move][EP_STATUS] >= 0) {
14516             *p++ = boards[move][EP_STATUS] + AAA;
14517             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14518         } else {
14519             *p++ = '-';
14520         }
14521     } else {
14522         *p++ = '-';
14523     }
14524     *p++ = ' ';
14525   }
14526   }
14527
14528     /* [HGM] find reversible plies */
14529     {   int i = 0, j=move;
14530
14531         if (appData.debugMode) { int k;
14532             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14533             for(k=backwardMostMove; k<=forwardMostMove; k++)
14534                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14535
14536         }
14537
14538         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14539         if( j == backwardMostMove ) i += initialRulePlies;
14540         sprintf(p, "%d ", i);
14541         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14542     }
14543     /* Fullmove number */
14544     sprintf(p, "%d", (move / 2) + 1);
14545     
14546     return StrSave(buf);
14547 }
14548
14549 Boolean
14550 ParseFEN(board, blackPlaysFirst, fen)
14551     Board board;
14552      int *blackPlaysFirst;
14553      char *fen;
14554 {
14555     int i, j;
14556     char *p;
14557     int emptycount;
14558     ChessSquare piece;
14559
14560     p = fen;
14561
14562     /* [HGM] by default clear Crazyhouse holdings, if present */
14563     if(gameInfo.holdingsWidth) {
14564        for(i=0; i<BOARD_HEIGHT; i++) {
14565            board[i][0]             = EmptySquare; /* black holdings */
14566            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14567            board[i][1]             = (ChessSquare) 0; /* black counts */
14568            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14569        }
14570     }
14571
14572     /* Piece placement data */
14573     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14574         j = 0;
14575         for (;;) {
14576             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14577                 if (*p == '/') p++;
14578                 emptycount = gameInfo.boardWidth - j;
14579                 while (emptycount--)
14580                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14581                 break;
14582 #if(BOARD_FILES >= 10)
14583             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14584                 p++; emptycount=10;
14585                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14586                 while (emptycount--)
14587                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14588 #endif
14589             } else if (isdigit(*p)) {
14590                 emptycount = *p++ - '0';
14591                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14592                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14593                 while (emptycount--)
14594                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14595             } else if (*p == '+' || isalpha(*p)) {
14596                 if (j >= gameInfo.boardWidth) return FALSE;
14597                 if(*p=='+') {
14598                     piece = CharToPiece(*++p);
14599                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14600                     piece = (ChessSquare) (PROMOTED piece ); p++;
14601                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14602                 } else piece = CharToPiece(*p++);
14603
14604                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14605                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14606                     piece = (ChessSquare) (PROMOTED piece);
14607                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14608                     p++;
14609                 }
14610                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14611             } else {
14612                 return FALSE;
14613             }
14614         }
14615     }
14616     while (*p == '/' || *p == ' ') p++;
14617
14618     /* [HGM] look for Crazyhouse holdings here */
14619     while(*p==' ') p++;
14620     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14621         if(*p == '[') p++;
14622         if(*p == '-' ) *p++; /* empty holdings */ else {
14623             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14624             /* if we would allow FEN reading to set board size, we would   */
14625             /* have to add holdings and shift the board read so far here   */
14626             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14627                 *p++;
14628                 if((int) piece >= (int) BlackPawn ) {
14629                     i = (int)piece - (int)BlackPawn;
14630                     i = PieceToNumber((ChessSquare)i);
14631                     if( i >= gameInfo.holdingsSize ) return FALSE;
14632                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14633                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14634                 } else {
14635                     i = (int)piece - (int)WhitePawn;
14636                     i = PieceToNumber((ChessSquare)i);
14637                     if( i >= gameInfo.holdingsSize ) return FALSE;
14638                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14639                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14640                 }
14641             }
14642         }
14643         if(*p == ']') *p++;
14644     }
14645
14646     while(*p == ' ') p++;
14647
14648     /* Active color */
14649     switch (*p++) {
14650       case 'w':
14651         *blackPlaysFirst = FALSE;
14652         break;
14653       case 'b': 
14654         *blackPlaysFirst = TRUE;
14655         break;
14656       default:
14657         return FALSE;
14658     }
14659
14660     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14661     /* return the extra info in global variiables             */
14662
14663     /* set defaults in case FEN is incomplete */
14664     board[EP_STATUS] = EP_UNKNOWN;
14665     for(i=0; i<nrCastlingRights; i++ ) {
14666         board[CASTLING][i] =
14667             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14668     }   /* assume possible unless obviously impossible */
14669     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14670     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14671     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14672                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14673     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14674     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14675     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14676                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14677     FENrulePlies = 0;
14678
14679     while(*p==' ') p++;
14680     if(nrCastlingRights) {
14681       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14682           /* castling indicator present, so default becomes no castlings */
14683           for(i=0; i<nrCastlingRights; i++ ) {
14684                  board[CASTLING][i] = NoRights;
14685           }
14686       }
14687       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14688              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14689              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14690              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14691         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14692
14693         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14694             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14695             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14696         }
14697         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14698             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14699         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14700                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14701         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14702                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14703         switch(c) {
14704           case'K':
14705               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14706               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14707               board[CASTLING][2] = whiteKingFile;
14708               break;
14709           case'Q':
14710               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14711               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14712               board[CASTLING][2] = whiteKingFile;
14713               break;
14714           case'k':
14715               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14716               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14717               board[CASTLING][5] = blackKingFile;
14718               break;
14719           case'q':
14720               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14721               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14722               board[CASTLING][5] = blackKingFile;
14723           case '-':
14724               break;
14725           default: /* FRC castlings */
14726               if(c >= 'a') { /* black rights */
14727                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14728                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14729                   if(i == BOARD_RGHT) break;
14730                   board[CASTLING][5] = i;
14731                   c -= AAA;
14732                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14733                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14734                   if(c > i)
14735                       board[CASTLING][3] = c;
14736                   else
14737                       board[CASTLING][4] = c;
14738               } else { /* white rights */
14739                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14740                     if(board[0][i] == WhiteKing) break;
14741                   if(i == BOARD_RGHT) break;
14742                   board[CASTLING][2] = i;
14743                   c -= AAA - 'a' + 'A';
14744                   if(board[0][c] >= WhiteKing) break;
14745                   if(c > i)
14746                       board[CASTLING][0] = c;
14747                   else
14748                       board[CASTLING][1] = c;
14749               }
14750         }
14751       }
14752       for(i=0; i<nrCastlingRights; i++)
14753         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14754     if (appData.debugMode) {
14755         fprintf(debugFP, "FEN castling rights:");
14756         for(i=0; i<nrCastlingRights; i++)
14757         fprintf(debugFP, " %d", board[CASTLING][i]);
14758         fprintf(debugFP, "\n");
14759     }
14760
14761       while(*p==' ') p++;
14762     }
14763
14764     /* read e.p. field in games that know e.p. capture */
14765     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14766        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14767       if(*p=='-') {
14768         p++; board[EP_STATUS] = EP_NONE;
14769       } else {
14770          char c = *p++ - AAA;
14771
14772          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14773          if(*p >= '0' && *p <='9') *p++;
14774          board[EP_STATUS] = c;
14775       }
14776     }
14777
14778
14779     if(sscanf(p, "%d", &i) == 1) {
14780         FENrulePlies = i; /* 50-move ply counter */
14781         /* (The move number is still ignored)    */
14782     }
14783
14784     return TRUE;
14785 }
14786       
14787 void
14788 EditPositionPasteFEN(char *fen)
14789 {
14790   if (fen != NULL) {
14791     Board initial_position;
14792
14793     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14794       DisplayError(_("Bad FEN position in clipboard"), 0);
14795       return ;
14796     } else {
14797       int savedBlackPlaysFirst = blackPlaysFirst;
14798       EditPositionEvent();
14799       blackPlaysFirst = savedBlackPlaysFirst;
14800       CopyBoard(boards[0], initial_position);
14801       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14802       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14803       DisplayBothClocks();
14804       DrawPosition(FALSE, boards[currentMove]);
14805     }
14806   }
14807 }
14808
14809 static char cseq[12] = "\\   ";
14810
14811 Boolean set_cont_sequence(char *new_seq)
14812 {
14813     int len;
14814     Boolean ret;
14815
14816     // handle bad attempts to set the sequence
14817         if (!new_seq)
14818                 return 0; // acceptable error - no debug
14819
14820     len = strlen(new_seq);
14821     ret = (len > 0) && (len < sizeof(cseq));
14822     if (ret)
14823         strcpy(cseq, new_seq);
14824     else if (appData.debugMode)
14825         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14826     return ret;
14827 }
14828
14829 /*
14830     reformat a source message so words don't cross the width boundary.  internal
14831     newlines are not removed.  returns the wrapped size (no null character unless
14832     included in source message).  If dest is NULL, only calculate the size required
14833     for the dest buffer.  lp argument indicats line position upon entry, and it's
14834     passed back upon exit.
14835 */
14836 int wrap(char *dest, char *src, int count, int width, int *lp)
14837 {
14838     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14839
14840     cseq_len = strlen(cseq);
14841     old_line = line = *lp;
14842     ansi = len = clen = 0;
14843
14844     for (i=0; i < count; i++)
14845     {
14846         if (src[i] == '\033')
14847             ansi = 1;
14848
14849         // if we hit the width, back up
14850         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14851         {
14852             // store i & len in case the word is too long
14853             old_i = i, old_len = len;
14854
14855             // find the end of the last word
14856             while (i && src[i] != ' ' && src[i] != '\n')
14857             {
14858                 i--;
14859                 len--;
14860             }
14861
14862             // word too long?  restore i & len before splitting it
14863             if ((old_i-i+clen) >= width)
14864             {
14865                 i = old_i;
14866                 len = old_len;
14867             }
14868
14869             // extra space?
14870             if (i && src[i-1] == ' ')
14871                 len--;
14872
14873             if (src[i] != ' ' && src[i] != '\n')
14874             {
14875                 i--;
14876                 if (len)
14877                     len--;
14878             }
14879
14880             // now append the newline and continuation sequence
14881             if (dest)
14882                 dest[len] = '\n';
14883             len++;
14884             if (dest)
14885                 strncpy(dest+len, cseq, cseq_len);
14886             len += cseq_len;
14887             line = cseq_len;
14888             clen = cseq_len;
14889             continue;
14890         }
14891
14892         if (dest)
14893             dest[len] = src[i];
14894         len++;
14895         if (!ansi)
14896             line++;
14897         if (src[i] == '\n')
14898             line = 0;
14899         if (src[i] == 'm')
14900             ansi = 0;
14901     }
14902     if (dest && appData.debugMode)
14903     {
14904         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14905             count, width, line, len, *lp);
14906         show_bytes(debugFP, src, count);
14907         fprintf(debugFP, "\ndest: ");
14908         show_bytes(debugFP, dest, len);
14909         fprintf(debugFP, "\n");
14910     }
14911     *lp = dest ? line : old_line;
14912
14913     return len;
14914 }
14915
14916 // [HGM] vari: routines for shelving variations
14917
14918 void 
14919 PushTail(int firstMove, int lastMove)
14920 {
14921         int i, j, nrMoves = lastMove - firstMove;
14922
14923         if(appData.icsActive) { // only in local mode
14924                 forwardMostMove = currentMove; // mimic old ICS behavior
14925                 return;
14926         }
14927         if(storedGames >= MAX_VARIATIONS-1) return;
14928
14929         // push current tail of game on stack
14930         savedResult[storedGames] = gameInfo.result;
14931         savedDetails[storedGames] = gameInfo.resultDetails;
14932         gameInfo.resultDetails = NULL;
14933         savedFirst[storedGames] = firstMove;
14934         savedLast [storedGames] = lastMove;
14935         savedFramePtr[storedGames] = framePtr;
14936         framePtr -= nrMoves; // reserve space for the boards
14937         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14938             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14939             for(j=0; j<MOVE_LEN; j++)
14940                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14941             for(j=0; j<2*MOVE_LEN; j++)
14942                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14943             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14944             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14945             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14946             pvInfoList[firstMove+i-1].depth = 0;
14947             commentList[framePtr+i] = commentList[firstMove+i];
14948             commentList[firstMove+i] = NULL;
14949         }
14950
14951         storedGames++;
14952         forwardMostMove = currentMove; // truncte game so we can start variation
14953         if(storedGames == 1) GreyRevert(FALSE);
14954 }
14955
14956 Boolean
14957 PopTail(Boolean annotate)
14958 {
14959         int i, j, nrMoves;
14960         char buf[8000], moveBuf[20];
14961
14962         if(appData.icsActive) return FALSE; // only in local mode
14963         if(!storedGames) return FALSE; // sanity
14964
14965         storedGames--;
14966         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14967         nrMoves = savedLast[storedGames] - currentMove;
14968         if(annotate) {
14969                 int cnt = 10;
14970                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14971                 else strcpy(buf, "(");
14972                 for(i=currentMove; i<forwardMostMove; i++) {
14973                         if(WhiteOnMove(i))
14974                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14975                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14976                         strcat(buf, moveBuf);
14977                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14978                 }
14979                 strcat(buf, ")");
14980         }
14981         for(i=1; i<nrMoves; i++) { // copy last variation back
14982             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14983             for(j=0; j<MOVE_LEN; j++)
14984                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14985             for(j=0; j<2*MOVE_LEN; j++)
14986                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14987             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14988             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14989             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14990             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14991             commentList[currentMove+i] = commentList[framePtr+i];
14992             commentList[framePtr+i] = NULL;
14993         }
14994         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14995         framePtr = savedFramePtr[storedGames];
14996         gameInfo.result = savedResult[storedGames];
14997         if(gameInfo.resultDetails != NULL) {
14998             free(gameInfo.resultDetails);
14999       }
15000         gameInfo.resultDetails = savedDetails[storedGames];
15001         forwardMostMove = currentMove + nrMoves;
15002         if(storedGames == 0) GreyRevert(TRUE);
15003         return TRUE;
15004 }
15005
15006 void 
15007 CleanupTail()
15008 {       // remove all shelved variations
15009         int i;
15010         for(i=0; i<storedGames; i++) {
15011             if(savedDetails[i])
15012                 free(savedDetails[i]);
15013             savedDetails[i] = NULL;
15014         }
15015         for(i=framePtr; i<MAX_MOVES; i++) {
15016                 if(commentList[i]) free(commentList[i]);
15017                 commentList[i] = NULL;
15018         }
15019         framePtr = MAX_MOVES-1;
15020         storedGames = 0;
15021 }