e2dddbc8ac5ed705d3b2707d593b4e94223ba353
[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, "* shouts:") ||
2744                                            looking_at(buf, &i, "* c-shouts:") ||
2745                                            looking_at(buf, &i, "--> * ") ||
2746                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2747                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2748                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2749                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2750                 int p;
2751                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2752                 chattingPartner = -1;
2753
2754                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2755                 for(p=0; p<MAX_CHAT; p++) {
2756                     if(channel == atoi(chatPartner[p])) {
2757                     talker[0] = '['; strcat(talker, "] ");
2758                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2759                     chattingPartner = p; break;
2760                     }
2761                 } else
2762                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2763                 for(p=0; p<MAX_CHAT; p++) {
2764                     if(!strcmp("whispers", chatPartner[p])) {
2765                         talker[0] = '['; strcat(talker, "] ");
2766                         chattingPartner = p; break;
2767                     }
2768                 } else
2769                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2770                 for(p=0; p<MAX_CHAT; p++) {
2771                     if(!strcmp("shouts", chatPartner[p])) {
2772                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2773                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2774                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2775                         chattingPartner = p; break;
2776                     }
2777                 }
2778                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2779                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2780                     talker[0] = 0; Colorize(ColorTell, FALSE);
2781                     chattingPartner = p; break;
2782                 }
2783                 if(chattingPartner<0) i = oldi; else {
2784                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2785                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2786                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2787                     started = STARTED_COMMENT;
2788                     parse_pos = 0; parse[0] = NULLCHAR;
2789                     savingComment = 3 + chattingPartner; // counts as TRUE
2790                     suppressKibitz = TRUE;
2791                     continue;
2792                 }
2793             } // [HGM] chat: end of patch
2794
2795             if (appData.zippyTalk || appData.zippyPlay) {
2796                 /* [DM] Backup address for color zippy lines */
2797                 backup = i;
2798 #if ZIPPY
2799        #ifdef WIN32
2800                if (loggedOn == TRUE)
2801                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2802                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2803        #else
2804                 if (ZippyControl(buf, &i) ||
2805                     ZippyConverse(buf, &i) ||
2806                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2807                       loggedOn = TRUE;
2808                       if (!appData.colorize) continue;
2809                 }
2810        #endif
2811 #endif
2812             } // [DM] 'else { ' deleted
2813                 if (
2814                     /* Regular tells and says */
2815                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2816                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2817                     looking_at(buf, &i, "* says: ") ||
2818                     /* Don't color "message" or "messages" output */
2819                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2820                     looking_at(buf, &i, "*. * at *:*: ") ||
2821                     looking_at(buf, &i, "--* (*:*): ") ||
2822                     /* Message notifications (same color as tells) */
2823                     looking_at(buf, &i, "* has left a message ") ||
2824                     looking_at(buf, &i, "* just sent you a message:\n") ||
2825                     /* Whispers and kibitzes */
2826                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2827                     looking_at(buf, &i, "* kibitzes: ") ||
2828                     /* Channel tells */
2829                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2830
2831                   if (tkind == 1 && strchr(star_match[0], ':')) {
2832                       /* Avoid "tells you:" spoofs in channels */
2833                      tkind = 3;
2834                   }
2835                   if (star_match[0][0] == NULLCHAR ||
2836                       strchr(star_match[0], ' ') ||
2837                       (tkind == 3 && strchr(star_match[1], ' '))) {
2838                     /* Reject bogus matches */
2839                     i = oldi;
2840                   } else {
2841                     if (appData.colorize) {
2842                       if (oldi > next_out) {
2843                         SendToPlayer(&buf[next_out], oldi - next_out);
2844                         next_out = oldi;
2845                       }
2846                       switch (tkind) {
2847                       case 1:
2848                         Colorize(ColorTell, FALSE);
2849                         curColor = ColorTell;
2850                         break;
2851                       case 2:
2852                         Colorize(ColorKibitz, FALSE);
2853                         curColor = ColorKibitz;
2854                         break;
2855                       case 3:
2856                         p = strrchr(star_match[1], '(');
2857                         if (p == NULL) {
2858                           p = star_match[1];
2859                         } else {
2860                           p++;
2861                         }
2862                         if (atoi(p) == 1) {
2863                           Colorize(ColorChannel1, FALSE);
2864                           curColor = ColorChannel1;
2865                         } else {
2866                           Colorize(ColorChannel, FALSE);
2867                           curColor = ColorChannel;
2868                         }
2869                         break;
2870                       case 5:
2871                         curColor = ColorNormal;
2872                         break;
2873                       }
2874                     }
2875                     if (started == STARTED_NONE && appData.autoComment &&
2876                         (gameMode == IcsObserving ||
2877                          gameMode == IcsPlayingWhite ||
2878                          gameMode == IcsPlayingBlack)) {
2879                       parse_pos = i - oldi;
2880                       memcpy(parse, &buf[oldi], parse_pos);
2881                       parse[parse_pos] = NULLCHAR;
2882                       started = STARTED_COMMENT;
2883                       savingComment = TRUE;
2884                     } else {
2885                       started = STARTED_CHATTER;
2886                       savingComment = FALSE;
2887                     }
2888                     loggedOn = TRUE;
2889                     continue;
2890                   }
2891                 }
2892
2893                 if (looking_at(buf, &i, "* s-shouts: ") ||
2894                     looking_at(buf, &i, "* c-shouts: ")) {
2895                     if (appData.colorize) {
2896                         if (oldi > next_out) {
2897                             SendToPlayer(&buf[next_out], oldi - next_out);
2898                             next_out = oldi;
2899                         }
2900                         Colorize(ColorSShout, FALSE);
2901                         curColor = ColorSShout;
2902                     }
2903                     loggedOn = TRUE;
2904                     started = STARTED_CHATTER;
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "--->")) {
2909                     loggedOn = TRUE;
2910                     continue;
2911                 }
2912
2913                 if (looking_at(buf, &i, "* shouts: ") ||
2914                     looking_at(buf, &i, "--> ")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorShout, FALSE);
2921                         curColor = ColorShout;
2922                     }
2923                     loggedOn = TRUE;
2924                     started = STARTED_CHATTER;
2925                     continue;
2926                 }
2927
2928                 if (looking_at( buf, &i, "Challenge:")) {
2929                     if (appData.colorize) {
2930                         if (oldi > next_out) {
2931                             SendToPlayer(&buf[next_out], oldi - next_out);
2932                             next_out = oldi;
2933                         }
2934                         Colorize(ColorChallenge, FALSE);
2935                         curColor = ColorChallenge;
2936                     }
2937                     loggedOn = TRUE;
2938                     continue;
2939                 }
2940
2941                 if (looking_at(buf, &i, "* offers you") ||
2942                     looking_at(buf, &i, "* offers to be") ||
2943                     looking_at(buf, &i, "* would like to") ||
2944                     looking_at(buf, &i, "* requests to") ||
2945                     looking_at(buf, &i, "Your opponent offers") ||
2946                     looking_at(buf, &i, "Your opponent requests")) {
2947
2948                     if (appData.colorize) {
2949                         if (oldi > next_out) {
2950                             SendToPlayer(&buf[next_out], oldi - next_out);
2951                             next_out = oldi;
2952                         }
2953                         Colorize(ColorRequest, FALSE);
2954                         curColor = ColorRequest;
2955                     }
2956                     continue;
2957                 }
2958
2959                 if (looking_at(buf, &i, "* (*) seeking")) {
2960                     if (appData.colorize) {
2961                         if (oldi > next_out) {
2962                             SendToPlayer(&buf[next_out], oldi - next_out);
2963                             next_out = oldi;
2964                         }
2965                         Colorize(ColorSeek, FALSE);
2966                         curColor = ColorSeek;
2967                     }
2968                     continue;
2969             }
2970
2971             if (looking_at(buf, &i, "\\   ")) {
2972                 if (prevColor != ColorNormal) {
2973                     if (oldi > next_out) {
2974                         SendToPlayer(&buf[next_out], oldi - next_out);
2975                         next_out = oldi;
2976                     }
2977                     Colorize(prevColor, TRUE);
2978                     curColor = prevColor;
2979                 }
2980                 if (savingComment) {
2981                     parse_pos = i - oldi;
2982                     memcpy(parse, &buf[oldi], parse_pos);
2983                     parse[parse_pos] = NULLCHAR;
2984                     started = STARTED_COMMENT;
2985                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2986                         chattingPartner = savingComment - 3; // kludge to remember the box
2987                 } else {
2988                     started = STARTED_CHATTER;
2989                 }
2990                 continue;
2991             }
2992
2993             if (looking_at(buf, &i, "Black Strength :") ||
2994                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2995                 looking_at(buf, &i, "<10>") ||
2996                 looking_at(buf, &i, "#@#")) {
2997                 /* Wrong board style */
2998                 loggedOn = TRUE;
2999                 SendToICS(ics_prefix);
3000                 SendToICS("set style 12\n");
3001                 SendToICS(ics_prefix);
3002                 SendToICS("refresh\n");
3003                 continue;
3004             }
3005             
3006             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3007                 ICSInitScript();
3008                 have_sent_ICS_logon = 1;
3009                 continue;
3010             }
3011               
3012             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3013                 (looking_at(buf, &i, "\n<12> ") ||
3014                  looking_at(buf, &i, "<12> "))) {
3015                 loggedOn = TRUE;
3016                 if (oldi > next_out) {
3017                     SendToPlayer(&buf[next_out], oldi - next_out);
3018                 }
3019                 next_out = i;
3020                 started = STARTED_BOARD;
3021                 parse_pos = 0;
3022                 continue;
3023             }
3024
3025             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3026                 looking_at(buf, &i, "<b1> ")) {
3027                 if (oldi > next_out) {
3028                     SendToPlayer(&buf[next_out], oldi - next_out);
3029                 }
3030                 next_out = i;
3031                 started = STARTED_HOLDINGS;
3032                 parse_pos = 0;
3033                 continue;
3034             }
3035
3036             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3037                 loggedOn = TRUE;
3038                 /* Header for a move list -- first line */
3039
3040                 switch (ics_getting_history) {
3041                   case H_FALSE:
3042                     switch (gameMode) {
3043                       case IcsIdle:
3044                       case BeginningOfGame:
3045                         /* User typed "moves" or "oldmoves" while we
3046                            were idle.  Pretend we asked for these
3047                            moves and soak them up so user can step
3048                            through them and/or save them.
3049                            */
3050                         Reset(FALSE, TRUE);
3051                         gameMode = IcsObserving;
3052                         ModeHighlight();
3053                         ics_gamenum = -1;
3054                         ics_getting_history = H_GOT_UNREQ_HEADER;
3055                         break;
3056                       case EditGame: /*?*/
3057                       case EditPosition: /*?*/
3058                         /* Should above feature work in these modes too? */
3059                         /* For now it doesn't */
3060                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3061                         break;
3062                       default:
3063                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3064                         break;
3065                     }
3066                     break;
3067                   case H_REQUESTED:
3068                     /* Is this the right one? */
3069                     if (gameInfo.white && gameInfo.black &&
3070                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3071                         strcmp(gameInfo.black, star_match[2]) == 0) {
3072                         /* All is well */
3073                         ics_getting_history = H_GOT_REQ_HEADER;
3074                     }
3075                     break;
3076                   case H_GOT_REQ_HEADER:
3077                   case H_GOT_UNREQ_HEADER:
3078                   case H_GOT_UNWANTED_HEADER:
3079                   case H_GETTING_MOVES:
3080                     /* Should not happen */
3081                     DisplayError(_("Error gathering move list: two headers"), 0);
3082                     ics_getting_history = H_FALSE;
3083                     break;
3084                 }
3085
3086                 /* Save player ratings into gameInfo if needed */
3087                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3088                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3089                     (gameInfo.whiteRating == -1 ||
3090                      gameInfo.blackRating == -1)) {
3091
3092                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3093                     gameInfo.blackRating = string_to_rating(star_match[3]);
3094                     if (appData.debugMode)
3095                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3096                               gameInfo.whiteRating, gameInfo.blackRating);
3097                 }
3098                 continue;
3099             }
3100
3101             if (looking_at(buf, &i,
3102               "* * match, initial time: * minute*, increment: * second")) {
3103                 /* Header for a move list -- second line */
3104                 /* Initial board will follow if this is a wild game */
3105                 if (gameInfo.event != NULL) free(gameInfo.event);
3106                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3107                 gameInfo.event = StrSave(str);
3108                 /* [HGM] we switched variant. Translate boards if needed. */
3109                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3110                 continue;
3111             }
3112
3113             if (looking_at(buf, &i, "Move  ")) {
3114                 /* Beginning of a move list */
3115                 switch (ics_getting_history) {
3116                   case H_FALSE:
3117                     /* Normally should not happen */
3118                     /* Maybe user hit reset while we were parsing */
3119                     break;
3120                   case H_REQUESTED:
3121                     /* Happens if we are ignoring a move list that is not
3122                      * the one we just requested.  Common if the user
3123                      * tries to observe two games without turning off
3124                      * getMoveList */
3125                     break;
3126                   case H_GETTING_MOVES:
3127                     /* Should not happen */
3128                     DisplayError(_("Error gathering move list: nested"), 0);
3129                     ics_getting_history = H_FALSE;
3130                     break;
3131                   case H_GOT_REQ_HEADER:
3132                     ics_getting_history = H_GETTING_MOVES;
3133                     started = STARTED_MOVES;
3134                     parse_pos = 0;
3135                     if (oldi > next_out) {
3136                         SendToPlayer(&buf[next_out], oldi - next_out);
3137                     }
3138                     break;
3139                   case H_GOT_UNREQ_HEADER:
3140                     ics_getting_history = H_GETTING_MOVES;
3141                     started = STARTED_MOVES_NOHIDE;
3142                     parse_pos = 0;
3143                     break;
3144                   case H_GOT_UNWANTED_HEADER:
3145                     ics_getting_history = H_FALSE;
3146                     break;
3147                 }
3148                 continue;
3149             }                           
3150             
3151             if (looking_at(buf, &i, "% ") ||
3152                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3153                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3154                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3155                     soughtPending = FALSE;
3156                     seekGraphUp = TRUE;
3157                     DrawSeekGraph();
3158                 }
3159                 if(suppressKibitz) next_out = i;
3160                 savingComment = FALSE;
3161                 suppressKibitz = 0;
3162                 switch (started) {
3163                   case STARTED_MOVES:
3164                   case STARTED_MOVES_NOHIDE:
3165                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3166                     parse[parse_pos + i - oldi] = NULLCHAR;
3167                     ParseGameHistory(parse);
3168 #if ZIPPY
3169                     if (appData.zippyPlay && first.initDone) {
3170                         FeedMovesToProgram(&first, forwardMostMove);
3171                         if (gameMode == IcsPlayingWhite) {
3172                             if (WhiteOnMove(forwardMostMove)) {
3173                                 if (first.sendTime) {
3174                                   if (first.useColors) {
3175                                     SendToProgram("black\n", &first); 
3176                                   }
3177                                   SendTimeRemaining(&first, TRUE);
3178                                 }
3179                                 if (first.useColors) {
3180                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3181                                 }
3182                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3183                                 first.maybeThinking = TRUE;
3184                             } else {
3185                                 if (first.usePlayother) {
3186                                   if (first.sendTime) {
3187                                     SendTimeRemaining(&first, TRUE);
3188                                   }
3189                                   SendToProgram("playother\n", &first);
3190                                   firstMove = FALSE;
3191                                 } else {
3192                                   firstMove = TRUE;
3193                                 }
3194                             }
3195                         } else if (gameMode == IcsPlayingBlack) {
3196                             if (!WhiteOnMove(forwardMostMove)) {
3197                                 if (first.sendTime) {
3198                                   if (first.useColors) {
3199                                     SendToProgram("white\n", &first);
3200                                   }
3201                                   SendTimeRemaining(&first, FALSE);
3202                                 }
3203                                 if (first.useColors) {
3204                                   SendToProgram("black\n", &first);
3205                                 }
3206                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3207                                 first.maybeThinking = TRUE;
3208                             } else {
3209                                 if (first.usePlayother) {
3210                                   if (first.sendTime) {
3211                                     SendTimeRemaining(&first, FALSE);
3212                                   }
3213                                   SendToProgram("playother\n", &first);
3214                                   firstMove = FALSE;
3215                                 } else {
3216                                   firstMove = TRUE;
3217                                 }
3218                             }
3219                         }                       
3220                     }
3221 #endif
3222                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3223                         /* Moves came from oldmoves or moves command
3224                            while we weren't doing anything else.
3225                            */
3226                         currentMove = forwardMostMove;
3227                         ClearHighlights();/*!!could figure this out*/
3228                         flipView = appData.flipView;
3229                         DrawPosition(TRUE, boards[currentMove]);
3230                         DisplayBothClocks();
3231                         sprintf(str, "%s vs. %s",
3232                                 gameInfo.white, gameInfo.black);
3233                         DisplayTitle(str);
3234                         gameMode = IcsIdle;
3235                     } else {
3236                         /* Moves were history of an active game */
3237                         if (gameInfo.resultDetails != NULL) {
3238                             free(gameInfo.resultDetails);
3239                             gameInfo.resultDetails = NULL;
3240                         }
3241                     }
3242                     HistorySet(parseList, backwardMostMove,
3243                                forwardMostMove, currentMove-1);
3244                     DisplayMove(currentMove - 1);
3245                     if (started == STARTED_MOVES) next_out = i;
3246                     started = STARTED_NONE;
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249
3250                   case STARTED_OBSERVE:
3251                     started = STARTED_NONE;
3252                     SendToICS(ics_prefix);
3253                     SendToICS("refresh\n");
3254                     break;
3255
3256                   default:
3257                     break;
3258                 }
3259                 if(bookHit) { // [HGM] book: simulate book reply
3260                     static char bookMove[MSG_SIZ]; // a bit generous?
3261
3262                     programStats.nodes = programStats.depth = programStats.time = 
3263                     programStats.score = programStats.got_only_move = 0;
3264                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3265
3266                     strcpy(bookMove, "move ");
3267                     strcat(bookMove, bookHit);
3268                     HandleMachineMove(bookMove, &first);
3269                 }
3270                 continue;
3271             }
3272             
3273             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3274                  started == STARTED_HOLDINGS ||
3275                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3276                 /* Accumulate characters in move list or board */
3277                 parse[parse_pos++] = buf[i];
3278             }
3279             
3280             /* Start of game messages.  Mostly we detect start of game
3281                when the first board image arrives.  On some versions
3282                of the ICS, though, we need to do a "refresh" after starting
3283                to observe in order to get the current board right away. */
3284             if (looking_at(buf, &i, "Adding game * to observation list")) {
3285                 started = STARTED_OBSERVE;
3286                 continue;
3287             }
3288
3289             /* Handle auto-observe */
3290             if (appData.autoObserve &&
3291                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3292                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3293                 char *player;
3294                 /* Choose the player that was highlighted, if any. */
3295                 if (star_match[0][0] == '\033' ||
3296                     star_match[1][0] != '\033') {
3297                     player = star_match[0];
3298                 } else {
3299                     player = star_match[2];
3300                 }
3301                 sprintf(str, "%sobserve %s\n",
3302                         ics_prefix, StripHighlightAndTitle(player));
3303                 SendToICS(str);
3304
3305                 /* Save ratings from notify string */
3306                 strcpy(player1Name, star_match[0]);
3307                 player1Rating = string_to_rating(star_match[1]);
3308                 strcpy(player2Name, star_match[2]);
3309                 player2Rating = string_to_rating(star_match[3]);
3310
3311                 if (appData.debugMode)
3312                   fprintf(debugFP, 
3313                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3314                           player1Name, player1Rating,
3315                           player2Name, player2Rating);
3316
3317                 continue;
3318             }
3319
3320             /* Deal with automatic examine mode after a game,
3321                and with IcsObserving -> IcsExamining transition */
3322             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3323                 looking_at(buf, &i, "has made you an examiner of game *")) {
3324
3325                 int gamenum = atoi(star_match[0]);
3326                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3327                     gamenum == ics_gamenum) {
3328                     /* We were already playing or observing this game;
3329                        no need to refetch history */
3330                     gameMode = IcsExamining;
3331                     if (pausing) {
3332                         pauseExamForwardMostMove = forwardMostMove;
3333                     } else if (currentMove < forwardMostMove) {
3334                         ForwardInner(forwardMostMove);
3335                     }
3336                 } else {
3337                     /* I don't think this case really can happen */
3338                     SendToICS(ics_prefix);
3339                     SendToICS("refresh\n");
3340                 }
3341                 continue;
3342             }    
3343             
3344             /* Error messages */
3345 //          if (ics_user_moved) {
3346             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3347                 if (looking_at(buf, &i, "Illegal move") ||
3348                     looking_at(buf, &i, "Not a legal move") ||
3349                     looking_at(buf, &i, "Your king is in check") ||
3350                     looking_at(buf, &i, "It isn't your turn") ||
3351                     looking_at(buf, &i, "It is not your move")) {
3352                     /* Illegal move */
3353                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3354                         currentMove = forwardMostMove-1;
3355                         DisplayMove(currentMove - 1); /* before DMError */
3356                         DrawPosition(FALSE, boards[currentMove]);
3357                         SwitchClocks(forwardMostMove-1); // [HGM] race
3358                         DisplayBothClocks();
3359                     }
3360                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3361                     ics_user_moved = 0;
3362                     continue;
3363                 }
3364             }
3365
3366             if (looking_at(buf, &i, "still have time") ||
3367                 looking_at(buf, &i, "not out of time") ||
3368                 looking_at(buf, &i, "either player is out of time") ||
3369                 looking_at(buf, &i, "has timeseal; checking")) {
3370                 /* We must have called his flag a little too soon */
3371                 whiteFlag = blackFlag = FALSE;
3372                 continue;
3373             }
3374
3375             if (looking_at(buf, &i, "added * seconds to") ||
3376                 looking_at(buf, &i, "seconds were added to")) {
3377                 /* Update the clocks */
3378                 SendToICS(ics_prefix);
3379                 SendToICS("refresh\n");
3380                 continue;
3381             }
3382
3383             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3384                 ics_clock_paused = TRUE;
3385                 StopClocks();
3386                 continue;
3387             }
3388
3389             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3390                 ics_clock_paused = FALSE;
3391                 StartClocks();
3392                 continue;
3393             }
3394
3395             /* Grab player ratings from the Creating: message.
3396                Note we have to check for the special case when
3397                the ICS inserts things like [white] or [black]. */
3398             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3399                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3400                 /* star_matches:
3401                    0    player 1 name (not necessarily white)
3402                    1    player 1 rating
3403                    2    empty, white, or black (IGNORED)
3404                    3    player 2 name (not necessarily black)
3405                    4    player 2 rating
3406                    
3407                    The names/ratings are sorted out when the game
3408                    actually starts (below).
3409                 */
3410                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3411                 player1Rating = string_to_rating(star_match[1]);
3412                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3413                 player2Rating = string_to_rating(star_match[4]);
3414
3415                 if (appData.debugMode)
3416                   fprintf(debugFP, 
3417                           "Ratings from 'Creating:' %s %d, %s %d\n",
3418                           player1Name, player1Rating,
3419                           player2Name, player2Rating);
3420
3421                 continue;
3422             }
3423             
3424             /* Improved generic start/end-of-game messages */
3425             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3426                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3427                 /* If tkind == 0: */
3428                 /* star_match[0] is the game number */
3429                 /*           [1] is the white player's name */
3430                 /*           [2] is the black player's name */
3431                 /* For end-of-game: */
3432                 /*           [3] is the reason for the game end */
3433                 /*           [4] is a PGN end game-token, preceded by " " */
3434                 /* For start-of-game: */
3435                 /*           [3] begins with "Creating" or "Continuing" */
3436                 /*           [4] is " *" or empty (don't care). */
3437                 int gamenum = atoi(star_match[0]);
3438                 char *whitename, *blackname, *why, *endtoken;
3439                 ChessMove endtype = (ChessMove) 0;
3440
3441                 if (tkind == 0) {
3442                   whitename = star_match[1];
3443                   blackname = star_match[2];
3444                   why = star_match[3];
3445                   endtoken = star_match[4];
3446                 } else {
3447                   whitename = star_match[1];
3448                   blackname = star_match[3];
3449                   why = star_match[5];
3450                   endtoken = star_match[6];
3451                 }
3452
3453                 /* Game start messages */
3454                 if (strncmp(why, "Creating ", 9) == 0 ||
3455                     strncmp(why, "Continuing ", 11) == 0) {
3456                     gs_gamenum = gamenum;
3457                     strcpy(gs_kind, strchr(why, ' ') + 1);
3458                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3459 #if ZIPPY
3460                     if (appData.zippyPlay) {
3461                         ZippyGameStart(whitename, blackname);
3462                     }
3463 #endif /*ZIPPY*/
3464                     continue;
3465                 }
3466
3467                 /* Game end messages */
3468                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3469                     ics_gamenum != gamenum) {
3470                     continue;
3471                 }
3472                 while (endtoken[0] == ' ') endtoken++;
3473                 switch (endtoken[0]) {
3474                   case '*':
3475                   default:
3476                     endtype = GameUnfinished;
3477                     break;
3478                   case '0':
3479                     endtype = BlackWins;
3480                     break;
3481                   case '1':
3482                     if (endtoken[1] == '/')
3483                       endtype = GameIsDrawn;
3484                     else
3485                       endtype = WhiteWins;
3486                     break;
3487                 }
3488                 GameEnds(endtype, why, GE_ICS);
3489 #if ZIPPY
3490                 if (appData.zippyPlay && first.initDone) {
3491                     ZippyGameEnd(endtype, why);
3492                     if (first.pr == NULL) {
3493                       /* Start the next process early so that we'll
3494                          be ready for the next challenge */
3495                       StartChessProgram(&first);
3496                     }
3497                     /* Send "new" early, in case this command takes
3498                        a long time to finish, so that we'll be ready
3499                        for the next challenge. */
3500                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3501                     Reset(TRUE, TRUE);
3502                 }
3503 #endif /*ZIPPY*/
3504                 continue;
3505             }
3506
3507             if (looking_at(buf, &i, "Removing game * from observation") ||
3508                 looking_at(buf, &i, "no longer observing game *") ||
3509                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3510                 if (gameMode == IcsObserving &&
3511                     atoi(star_match[0]) == ics_gamenum)
3512                   {
3513                       /* icsEngineAnalyze */
3514                       if (appData.icsEngineAnalyze) {
3515                             ExitAnalyzeMode();
3516                             ModeHighlight();
3517                       }
3518                       StopClocks();
3519                       gameMode = IcsIdle;
3520                       ics_gamenum = -1;
3521                       ics_user_moved = FALSE;
3522                   }
3523                 continue;
3524             }
3525
3526             if (looking_at(buf, &i, "no longer examining game *")) {
3527                 if (gameMode == IcsExamining &&
3528                     atoi(star_match[0]) == ics_gamenum)
3529                   {
3530                       gameMode = IcsIdle;
3531                       ics_gamenum = -1;
3532                       ics_user_moved = FALSE;
3533                   }
3534                 continue;
3535             }
3536
3537             /* Advance leftover_start past any newlines we find,
3538                so only partial lines can get reparsed */
3539             if (looking_at(buf, &i, "\n")) {
3540                 prevColor = curColor;
3541                 if (curColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(ColorNormal, FALSE);
3547                     curColor = ColorNormal;
3548                 }
3549                 if (started == STARTED_BOARD) {
3550                     started = STARTED_NONE;
3551                     parse[parse_pos] = NULLCHAR;
3552                     ParseBoard12(parse);
3553                     ics_user_moved = 0;
3554
3555                     /* Send premove here */
3556                     if (appData.premove) {
3557                       char str[MSG_SIZ];
3558                       if (currentMove == 0 &&
3559                           gameMode == IcsPlayingWhite &&
3560                           appData.premoveWhite) {
3561                         sprintf(str, "%s\n", appData.premoveWhiteText);
3562                         if (appData.debugMode)
3563                           fprintf(debugFP, "Sending premove:\n");
3564                         SendToICS(str);
3565                       } else if (currentMove == 1 &&
3566                                  gameMode == IcsPlayingBlack &&
3567                                  appData.premoveBlack) {
3568                         sprintf(str, "%s\n", appData.premoveBlackText);
3569                         if (appData.debugMode)
3570                           fprintf(debugFP, "Sending premove:\n");
3571                         SendToICS(str);
3572                       } else if (gotPremove) {
3573                         gotPremove = 0;
3574                         ClearPremoveHighlights();
3575                         if (appData.debugMode)
3576                           fprintf(debugFP, "Sending premove:\n");
3577                           UserMoveEvent(premoveFromX, premoveFromY, 
3578                                         premoveToX, premoveToY, 
3579                                         premovePromoChar);
3580                       }
3581                     }
3582
3583                     /* Usually suppress following prompt */
3584                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3585                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3586                         if (looking_at(buf, &i, "*% ")) {
3587                             savingComment = FALSE;
3588                             suppressKibitz = 0;
3589                         }
3590                     }
3591                     next_out = i;
3592                 } else if (started == STARTED_HOLDINGS) {
3593                     int gamenum;
3594                     char new_piece[MSG_SIZ];
3595                     started = STARTED_NONE;
3596                     parse[parse_pos] = NULLCHAR;
3597                     if (appData.debugMode)
3598                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3599                                                         parse, currentMove);
3600                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3601                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3602                         if (gameInfo.variant == VariantNormal) {
3603                           /* [HGM] We seem to switch variant during a game!
3604                            * Presumably no holdings were displayed, so we have
3605                            * to move the position two files to the right to
3606                            * create room for them!
3607                            */
3608                           VariantClass newVariant;
3609                           switch(gameInfo.boardWidth) { // base guess on board width
3610                                 case 9:  newVariant = VariantShogi; break;
3611                                 case 10: newVariant = VariantGreat; break;
3612                                 default: newVariant = VariantCrazyhouse; break;
3613                           }
3614                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3615                           /* Get a move list just to see the header, which
3616                              will tell us whether this is really bug or zh */
3617                           if (ics_getting_history == H_FALSE) {
3618                             ics_getting_history = H_REQUESTED;
3619                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3620                             SendToICS(str);
3621                           }
3622                         }
3623                         new_piece[0] = NULLCHAR;
3624                         sscanf(parse, "game %d white [%s black [%s <- %s",
3625                                &gamenum, white_holding, black_holding,
3626                                new_piece);
3627                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3628                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3629                         /* [HGM] copy holdings to board holdings area */
3630                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3631                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3632                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3633 #if ZIPPY
3634                         if (appData.zippyPlay && first.initDone) {
3635                             ZippyHoldings(white_holding, black_holding,
3636                                           new_piece);
3637                         }
3638 #endif /*ZIPPY*/
3639                         if (tinyLayout || smallLayout) {
3640                             char wh[16], bh[16];
3641                             PackHolding(wh, white_holding);
3642                             PackHolding(bh, black_holding);
3643                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3644                                     gameInfo.white, gameInfo.black);
3645                         } else {
3646                             sprintf(str, "%s [%s] vs. %s [%s]",
3647                                     gameInfo.white, white_holding,
3648                                     gameInfo.black, black_holding);
3649                         }
3650
3651                         DrawPosition(FALSE, boards[currentMove]);
3652                         DisplayTitle(str);
3653                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3654                         sscanf(parse, "game %d white [%s black [%s <- %s",
3655                                &gamenum, white_holding, black_holding,
3656                                new_piece);
3657                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3658                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3659                         /* [HGM] copy holdings to partner-board holdings area */
3660                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3661                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3662                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3663                       }
3664                     }
3665                     /* Suppress following prompt */
3666                     if (looking_at(buf, &i, "*% ")) {
3667                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3668                         savingComment = FALSE;
3669                         suppressKibitz = 0;
3670                     }
3671                     next_out = i;
3672                 }
3673                 continue;
3674             }
3675
3676             i++;                /* skip unparsed character and loop back */
3677         }
3678         
3679         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3680 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3681 //          SendToPlayer(&buf[next_out], i - next_out);
3682             started != STARTED_HOLDINGS && leftover_start > next_out) {
3683             SendToPlayer(&buf[next_out], leftover_start - next_out);
3684             next_out = i;
3685         }
3686         
3687         leftover_len = buf_len - leftover_start;
3688         /* if buffer ends with something we couldn't parse,
3689            reparse it after appending the next read */
3690         
3691     } else if (count == 0) {
3692         RemoveInputSource(isr);
3693         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3694     } else {
3695         DisplayFatalError(_("Error reading from ICS"), error, 1);
3696     }
3697 }
3698
3699
3700 /* Board style 12 looks like this:
3701    
3702    <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
3703    
3704  * The "<12> " is stripped before it gets to this routine.  The two
3705  * trailing 0's (flip state and clock ticking) are later addition, and
3706  * some chess servers may not have them, or may have only the first.
3707  * Additional trailing fields may be added in the future.  
3708  */
3709
3710 #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"
3711
3712 #define RELATION_OBSERVING_PLAYED    0
3713 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3714 #define RELATION_PLAYING_MYMOVE      1
3715 #define RELATION_PLAYING_NOTMYMOVE  -1
3716 #define RELATION_EXAMINING           2
3717 #define RELATION_ISOLATED_BOARD     -3
3718 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3719
3720 void
3721 ParseBoard12(string)
3722      char *string;
3723
3724     GameMode newGameMode;
3725     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3726     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3727     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3728     char to_play, board_chars[200];
3729     char move_str[500], str[500], elapsed_time[500];
3730     char black[32], white[32];
3731     Board board;
3732     int prevMove = currentMove;
3733     int ticking = 2;
3734     ChessMove moveType;
3735     int fromX, fromY, toX, toY;
3736     char promoChar;
3737     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3738     char *bookHit = NULL; // [HGM] book
3739     Boolean weird = FALSE, reqFlag = FALSE;
3740
3741     fromX = fromY = toX = toY = -1;
3742     
3743     newGame = FALSE;
3744
3745     if (appData.debugMode)
3746       fprintf(debugFP, _("Parsing board: %s\n"), string);
3747
3748     move_str[0] = NULLCHAR;
3749     elapsed_time[0] = NULLCHAR;
3750     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3751         int  i = 0, j;
3752         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3753             if(string[i] == ' ') { ranks++; files = 0; }
3754             else files++;
3755             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3756             i++;
3757         }
3758         for(j = 0; j <i; j++) board_chars[j] = string[j];
3759         board_chars[i] = '\0';
3760         string += i + 1;
3761     }
3762     n = sscanf(string, PATTERN, &to_play, &double_push,
3763                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3764                &gamenum, white, black, &relation, &basetime, &increment,
3765                &white_stren, &black_stren, &white_time, &black_time,
3766                &moveNum, str, elapsed_time, move_str, &ics_flip,
3767                &ticking);
3768
3769     if (n < 21) {
3770         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3771         DisplayError(str, 0);
3772         return;
3773     }
3774
3775     /* Convert the move number to internal form */
3776     moveNum = (moveNum - 1) * 2;
3777     if (to_play == 'B') moveNum++;
3778     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3779       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3780                         0, 1);
3781       return;
3782     }
3783     
3784     switch (relation) {
3785       case RELATION_OBSERVING_PLAYED:
3786       case RELATION_OBSERVING_STATIC:
3787         if (gamenum == -1) {
3788             /* Old ICC buglet */
3789             relation = RELATION_OBSERVING_STATIC;
3790         }
3791         newGameMode = IcsObserving;
3792         break;
3793       case RELATION_PLAYING_MYMOVE:
3794       case RELATION_PLAYING_NOTMYMOVE:
3795         newGameMode =
3796           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3797             IcsPlayingWhite : IcsPlayingBlack;
3798         break;
3799       case RELATION_EXAMINING:
3800         newGameMode = IcsExamining;
3801         break;
3802       case RELATION_ISOLATED_BOARD:
3803       default:
3804         /* Just display this board.  If user was doing something else,
3805            we will forget about it until the next board comes. */ 
3806         newGameMode = IcsIdle;
3807         break;
3808       case RELATION_STARTING_POSITION:
3809         newGameMode = gameMode;
3810         break;
3811     }
3812     
3813     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3814          && newGameMode == IcsObserving && appData.bgObserve) {
3815       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3816       char buf[MSG_SIZ];
3817       for (k = 0; k < ranks; k++) {
3818         for (j = 0; j < files; j++)
3819           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3820         if(gameInfo.holdingsWidth > 1) {
3821              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3822              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3823         }
3824       }
3825       CopyBoard(partnerBoard, board);
3826       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3827       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3828                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3829       DisplayMessage(buf, "");
3830       return;
3831     }
3832
3833     /* Modify behavior for initial board display on move listing
3834        of wild games.
3835        */
3836     switch (ics_getting_history) {
3837       case H_FALSE:
3838       case H_REQUESTED:
3839         break;
3840       case H_GOT_REQ_HEADER:
3841       case H_GOT_UNREQ_HEADER:
3842         /* This is the initial position of the current game */
3843         gamenum = ics_gamenum;
3844         moveNum = 0;            /* old ICS bug workaround */
3845         if (to_play == 'B') {
3846           startedFromSetupPosition = TRUE;
3847           blackPlaysFirst = TRUE;
3848           moveNum = 1;
3849           if (forwardMostMove == 0) forwardMostMove = 1;
3850           if (backwardMostMove == 0) backwardMostMove = 1;
3851           if (currentMove == 0) currentMove = 1;
3852         }
3853         newGameMode = gameMode;
3854         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3855         break;
3856       case H_GOT_UNWANTED_HEADER:
3857         /* This is an initial board that we don't want */
3858         return;
3859       case H_GETTING_MOVES:
3860         /* Should not happen */
3861         DisplayError(_("Error gathering move list: extra board"), 0);
3862         ics_getting_history = H_FALSE;
3863         return;
3864     }
3865
3866    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3867                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3868      /* [HGM] We seem to have switched variant unexpectedly
3869       * Try to guess new variant from board size
3870       */
3871           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3872           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3873           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3874           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3875           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3876           if(!weird) newVariant = VariantNormal;
3877           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3878           /* Get a move list just to see the header, which
3879              will tell us whether this is really bug or zh */
3880           if (ics_getting_history == H_FALSE) {
3881             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3882             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3883             SendToICS(str);
3884           }
3885     }
3886     
3887     /* Take action if this is the first board of a new game, or of a
3888        different game than is currently being displayed.  */
3889     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3890         relation == RELATION_ISOLATED_BOARD) {
3891         
3892         /* Forget the old game and get the history (if any) of the new one */
3893         if (gameMode != BeginningOfGame) {
3894           Reset(TRUE, TRUE);
3895         }
3896         newGame = TRUE;
3897         if (appData.autoRaiseBoard) BoardToTop();
3898         prevMove = -3;
3899         if (gamenum == -1) {
3900             newGameMode = IcsIdle;
3901         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3902                    appData.getMoveList && !reqFlag) {
3903             /* Need to get game history */
3904             ics_getting_history = H_REQUESTED;
3905             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3906             SendToICS(str);
3907         }
3908         
3909         /* Initially flip the board to have black on the bottom if playing
3910            black or if the ICS flip flag is set, but let the user change
3911            it with the Flip View button. */
3912         flipView = appData.autoFlipView ? 
3913           (newGameMode == IcsPlayingBlack) || ics_flip :
3914           appData.flipView;
3915         
3916         /* Done with values from previous mode; copy in new ones */
3917         gameMode = newGameMode;
3918         ModeHighlight();
3919         ics_gamenum = gamenum;
3920         if (gamenum == gs_gamenum) {
3921             int klen = strlen(gs_kind);
3922             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3923             sprintf(str, "ICS %s", gs_kind);
3924             gameInfo.event = StrSave(str);
3925         } else {
3926             gameInfo.event = StrSave("ICS game");
3927         }
3928         gameInfo.site = StrSave(appData.icsHost);
3929         gameInfo.date = PGNDate();
3930         gameInfo.round = StrSave("-");
3931         gameInfo.white = StrSave(white);
3932         gameInfo.black = StrSave(black);
3933         timeControl = basetime * 60 * 1000;
3934         timeControl_2 = 0;
3935         timeIncrement = increment * 1000;
3936         movesPerSession = 0;
3937         gameInfo.timeControl = TimeControlTagValue();
3938         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3941     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3942     setbuf(debugFP, NULL);
3943   }
3944
3945         gameInfo.outOfBook = NULL;
3946         
3947         /* Do we have the ratings? */
3948         if (strcmp(player1Name, white) == 0 &&
3949             strcmp(player2Name, black) == 0) {
3950             if (appData.debugMode)
3951               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3952                       player1Rating, player2Rating);
3953             gameInfo.whiteRating = player1Rating;
3954             gameInfo.blackRating = player2Rating;
3955         } else if (strcmp(player2Name, white) == 0 &&
3956                    strcmp(player1Name, black) == 0) {
3957             if (appData.debugMode)
3958               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3959                       player2Rating, player1Rating);
3960             gameInfo.whiteRating = player2Rating;
3961             gameInfo.blackRating = player1Rating;
3962         }
3963         player1Name[0] = player2Name[0] = NULLCHAR;
3964
3965         /* Silence shouts if requested */
3966         if (appData.quietPlay &&
3967             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3968             SendToICS(ics_prefix);
3969             SendToICS("set shout 0\n");
3970         }
3971     }
3972     
3973     /* Deal with midgame name changes */
3974     if (!newGame) {
3975         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3976             if (gameInfo.white) free(gameInfo.white);
3977             gameInfo.white = StrSave(white);
3978         }
3979         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3980             if (gameInfo.black) free(gameInfo.black);
3981             gameInfo.black = StrSave(black);
3982         }
3983     }
3984     
3985     /* Throw away game result if anything actually changes in examine mode */
3986     if (gameMode == IcsExamining && !newGame) {
3987         gameInfo.result = GameUnfinished;
3988         if (gameInfo.resultDetails != NULL) {
3989             free(gameInfo.resultDetails);
3990             gameInfo.resultDetails = NULL;
3991         }
3992     }
3993     
3994     /* In pausing && IcsExamining mode, we ignore boards coming
3995        in if they are in a different variation than we are. */
3996     if (pauseExamInvalid) return;
3997     if (pausing && gameMode == IcsExamining) {
3998         if (moveNum <= pauseExamForwardMostMove) {
3999             pauseExamInvalid = TRUE;
4000             forwardMostMove = pauseExamForwardMostMove;
4001             return;
4002         }
4003     }
4004     
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4007   }
4008     /* Parse the board */
4009     for (k = 0; k < ranks; k++) {
4010       for (j = 0; j < files; j++)
4011         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4012       if(gameInfo.holdingsWidth > 1) {
4013            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4014            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4015       }
4016     }
4017     CopyBoard(boards[moveNum], board);
4018     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4019     if (moveNum == 0) {
4020         startedFromSetupPosition =
4021           !CompareBoards(board, initialPosition);
4022         if(startedFromSetupPosition)
4023             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4024     }
4025
4026     /* [HGM] Set castling rights. Take the outermost Rooks,
4027        to make it also work for FRC opening positions. Note that board12
4028        is really defective for later FRC positions, as it has no way to
4029        indicate which Rook can castle if they are on the same side of King.
4030        For the initial position we grant rights to the outermost Rooks,
4031        and remember thos rights, and we then copy them on positions
4032        later in an FRC game. This means WB might not recognize castlings with
4033        Rooks that have moved back to their original position as illegal,
4034        but in ICS mode that is not its job anyway.
4035     */
4036     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4037     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4038
4039         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4040             if(board[0][i] == WhiteRook) j = i;
4041         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4042         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4043             if(board[0][i] == WhiteRook) j = i;
4044         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4045         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4046             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4047         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4048         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4049             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4050         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4051
4052         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4053         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4054             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4055         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4056             if(board[BOARD_HEIGHT-1][k] == bKing)
4057                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4058         if(gameInfo.variant == VariantTwoKings) {
4059             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4060             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4061             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4062         }
4063     } else { int r;
4064         r = boards[moveNum][CASTLING][0] = initialRights[0];
4065         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4066         r = boards[moveNum][CASTLING][1] = initialRights[1];
4067         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4068         r = boards[moveNum][CASTLING][3] = initialRights[3];
4069         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4070         r = boards[moveNum][CASTLING][4] = initialRights[4];
4071         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4072         /* wildcastle kludge: always assume King has rights */
4073         r = boards[moveNum][CASTLING][2] = initialRights[2];
4074         r = boards[moveNum][CASTLING][5] = initialRights[5];
4075     }
4076     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4077     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4078
4079     
4080     if (ics_getting_history == H_GOT_REQ_HEADER ||
4081         ics_getting_history == H_GOT_UNREQ_HEADER) {
4082         /* This was an initial position from a move list, not
4083            the current position */
4084         return;
4085     }
4086     
4087     /* Update currentMove and known move number limits */
4088     newMove = newGame || moveNum > forwardMostMove;
4089
4090     if (newGame) {
4091         forwardMostMove = backwardMostMove = currentMove = moveNum;
4092         if (gameMode == IcsExamining && moveNum == 0) {
4093           /* Workaround for ICS limitation: we are not told the wild
4094              type when starting to examine a game.  But if we ask for
4095              the move list, the move list header will tell us */
4096             ics_getting_history = H_REQUESTED;
4097             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4098             SendToICS(str);
4099         }
4100     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4101                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4102 #if ZIPPY
4103         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4104         /* [HGM] applied this also to an engine that is silently watching        */
4105         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4106             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4107             gameInfo.variant == currentlyInitializedVariant) {
4108           takeback = forwardMostMove - moveNum;
4109           for (i = 0; i < takeback; i++) {
4110             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4111             SendToProgram("undo\n", &first);
4112           }
4113         }
4114 #endif
4115
4116         forwardMostMove = moveNum;
4117         if (!pausing || currentMove > forwardMostMove)
4118           currentMove = forwardMostMove;
4119     } else {
4120         /* New part of history that is not contiguous with old part */ 
4121         if (pausing && gameMode == IcsExamining) {
4122             pauseExamInvalid = TRUE;
4123             forwardMostMove = pauseExamForwardMostMove;
4124             return;
4125         }
4126         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4127 #if ZIPPY
4128             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4129                 // [HGM] when we will receive the move list we now request, it will be
4130                 // fed to the engine from the first move on. So if the engine is not
4131                 // in the initial position now, bring it there.
4132                 InitChessProgram(&first, 0);
4133             }
4134 #endif
4135             ics_getting_history = H_REQUESTED;
4136             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4137             SendToICS(str);
4138         }
4139         forwardMostMove = backwardMostMove = currentMove = moveNum;
4140     }
4141     
4142     /* Update the clocks */
4143     if (strchr(elapsed_time, '.')) {
4144       /* Time is in ms */
4145       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4146       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4147     } else {
4148       /* Time is in seconds */
4149       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4150       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4151     }
4152       
4153
4154 #if ZIPPY
4155     if (appData.zippyPlay && newGame &&
4156         gameMode != IcsObserving && gameMode != IcsIdle &&
4157         gameMode != IcsExamining)
4158       ZippyFirstBoard(moveNum, basetime, increment);
4159 #endif
4160     
4161     /* Put the move on the move list, first converting
4162        to canonical algebraic form. */
4163     if (moveNum > 0) {
4164   if (appData.debugMode) {
4165     if (appData.debugMode) { int f = forwardMostMove;
4166         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4167                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4168                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4169     }
4170     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4171     fprintf(debugFP, "moveNum = %d\n", moveNum);
4172     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4173     setbuf(debugFP, NULL);
4174   }
4175         if (moveNum <= backwardMostMove) {
4176             /* We don't know what the board looked like before
4177                this move.  Punt. */
4178             strcpy(parseList[moveNum - 1], move_str);
4179             strcat(parseList[moveNum - 1], " ");
4180             strcat(parseList[moveNum - 1], elapsed_time);
4181             moveList[moveNum - 1][0] = NULLCHAR;
4182         } else if (strcmp(move_str, "none") == 0) {
4183             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4184             /* Again, we don't know what the board looked like;
4185                this is really the start of the game. */
4186             parseList[moveNum - 1][0] = NULLCHAR;
4187             moveList[moveNum - 1][0] = NULLCHAR;
4188             backwardMostMove = moveNum;
4189             startedFromSetupPosition = TRUE;
4190             fromX = fromY = toX = toY = -1;
4191         } else {
4192           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4193           //                 So we parse the long-algebraic move string in stead of the SAN move
4194           int valid; char buf[MSG_SIZ], *prom;
4195
4196           // str looks something like "Q/a1-a2"; kill the slash
4197           if(str[1] == '/') 
4198                 sprintf(buf, "%c%s", str[0], str+2);
4199           else  strcpy(buf, str); // might be castling
4200           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4201                 strcat(buf, prom); // long move lacks promo specification!
4202           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4203                 if(appData.debugMode) 
4204                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4205                 strcpy(move_str, buf);
4206           }
4207           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4208                                 &fromX, &fromY, &toX, &toY, &promoChar)
4209                || ParseOneMove(buf, moveNum - 1, &moveType,
4210                                 &fromX, &fromY, &toX, &toY, &promoChar);
4211           // end of long SAN patch
4212           if (valid) {
4213             (void) CoordsToAlgebraic(boards[moveNum - 1],
4214                                      PosFlags(moveNum - 1),
4215                                      fromY, fromX, toY, toX, promoChar,
4216                                      parseList[moveNum-1]);
4217             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4218               case MT_NONE:
4219               case MT_STALEMATE:
4220               default:
4221                 break;
4222               case MT_CHECK:
4223                 if(gameInfo.variant != VariantShogi)
4224                     strcat(parseList[moveNum - 1], "+");
4225                 break;
4226               case MT_CHECKMATE:
4227               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4228                 strcat(parseList[moveNum - 1], "#");
4229                 break;
4230             }
4231             strcat(parseList[moveNum - 1], " ");
4232             strcat(parseList[moveNum - 1], elapsed_time);
4233             /* currentMoveString is set as a side-effect of ParseOneMove */
4234             strcpy(moveList[moveNum - 1], currentMoveString);
4235             strcat(moveList[moveNum - 1], "\n");
4236           } else {
4237             /* Move from ICS was illegal!?  Punt. */
4238   if (appData.debugMode) {
4239     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4241   }
4242             strcpy(parseList[moveNum - 1], move_str);
4243             strcat(parseList[moveNum - 1], " ");
4244             strcat(parseList[moveNum - 1], elapsed_time);
4245             moveList[moveNum - 1][0] = NULLCHAR;
4246             fromX = fromY = toX = toY = -1;
4247           }
4248         }
4249   if (appData.debugMode) {
4250     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4251     setbuf(debugFP, NULL);
4252   }
4253
4254 #if ZIPPY
4255         /* Send move to chess program (BEFORE animating it). */
4256         if (appData.zippyPlay && !newGame && newMove && 
4257            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4258
4259             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4260                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4261                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4262                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4263                             move_str);
4264                     DisplayError(str, 0);
4265                 } else {
4266                     if (first.sendTime) {
4267                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4268                     }
4269                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4270                     if (firstMove && !bookHit) {
4271                         firstMove = FALSE;
4272                         if (first.useColors) {
4273                           SendToProgram(gameMode == IcsPlayingWhite ?
4274                                         "white\ngo\n" :
4275                                         "black\ngo\n", &first);
4276                         } else {
4277                           SendToProgram("go\n", &first);
4278                         }
4279                         first.maybeThinking = TRUE;
4280                     }
4281                 }
4282             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4283               if (moveList[moveNum - 1][0] == NULLCHAR) {
4284                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4285                 DisplayError(str, 0);
4286               } else {
4287                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4288                 SendMoveToProgram(moveNum - 1, &first);
4289               }
4290             }
4291         }
4292 #endif
4293     }
4294
4295     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4296         /* If move comes from a remote source, animate it.  If it
4297            isn't remote, it will have already been animated. */
4298         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4299             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4300         }
4301         if (!pausing && appData.highlightLastMove) {
4302             SetHighlights(fromX, fromY, toX, toY);
4303         }
4304     }
4305     
4306     /* Start the clocks */
4307     whiteFlag = blackFlag = FALSE;
4308     appData.clockMode = !(basetime == 0 && increment == 0);
4309     if (ticking == 0) {
4310       ics_clock_paused = TRUE;
4311       StopClocks();
4312     } else if (ticking == 1) {
4313       ics_clock_paused = FALSE;
4314     }
4315     if (gameMode == IcsIdle ||
4316         relation == RELATION_OBSERVING_STATIC ||
4317         relation == RELATION_EXAMINING ||
4318         ics_clock_paused)
4319       DisplayBothClocks();
4320     else
4321       StartClocks();
4322     
4323     /* Display opponents and material strengths */
4324     if (gameInfo.variant != VariantBughouse &&
4325         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4326         if (tinyLayout || smallLayout) {
4327             if(gameInfo.variant == VariantNormal)
4328                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4329                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4330                     basetime, increment);
4331             else
4332                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4333                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4334                     basetime, increment, (int) gameInfo.variant);
4335         } else {
4336             if(gameInfo.variant == VariantNormal)
4337                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment);
4340             else
4341                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4342                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4343                     basetime, increment, VariantName(gameInfo.variant));
4344         }
4345         DisplayTitle(str);
4346   if (appData.debugMode) {
4347     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4348   }
4349     }
4350
4351
4352     /* Display the board */
4353     if (!pausing && !appData.noGUI) {
4354       
4355       if (appData.premove)
4356           if (!gotPremove || 
4357              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4358              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4359               ClearPremoveHighlights();
4360
4361       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4362       DrawPosition(j, boards[currentMove]);
4363
4364       DisplayMove(moveNum - 1);
4365       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4366             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4367               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4368         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4369       }
4370     }
4371
4372     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4373 #if ZIPPY
4374     if(bookHit) { // [HGM] book: simulate book reply
4375         static char bookMove[MSG_SIZ]; // a bit generous?
4376
4377         programStats.nodes = programStats.depth = programStats.time = 
4378         programStats.score = programStats.got_only_move = 0;
4379         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4380
4381         strcpy(bookMove, "move ");
4382         strcat(bookMove, bookHit);
4383         HandleMachineMove(bookMove, &first);
4384     }
4385 #endif
4386 }
4387
4388 void
4389 GetMoveListEvent()
4390 {
4391     char buf[MSG_SIZ];
4392     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4393         ics_getting_history = H_REQUESTED;
4394         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4395         SendToICS(buf);
4396     }
4397 }
4398
4399 void
4400 AnalysisPeriodicEvent(force)
4401      int force;
4402 {
4403     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4404          && !force) || !appData.periodicUpdates)
4405       return;
4406
4407     /* Send . command to Crafty to collect stats */
4408     SendToProgram(".\n", &first);
4409
4410     /* Don't send another until we get a response (this makes
4411        us stop sending to old Crafty's which don't understand
4412        the "." command (sending illegal cmds resets node count & time,
4413        which looks bad)) */
4414     programStats.ok_to_send = 0;
4415 }
4416
4417 void ics_update_width(new_width)
4418         int new_width;
4419 {
4420         ics_printf("set width %d\n", new_width);
4421 }
4422
4423 void
4424 SendMoveToProgram(moveNum, cps)
4425      int moveNum;
4426      ChessProgramState *cps;
4427 {
4428     char buf[MSG_SIZ];
4429
4430     if (cps->useUsermove) {
4431       SendToProgram("usermove ", cps);
4432     }
4433     if (cps->useSAN) {
4434       char *space;
4435       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4436         int len = space - parseList[moveNum];
4437         memcpy(buf, parseList[moveNum], len);
4438         buf[len++] = '\n';
4439         buf[len] = NULLCHAR;
4440       } else {
4441         sprintf(buf, "%s\n", parseList[moveNum]);
4442       }
4443       SendToProgram(buf, cps);
4444     } else {
4445       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4446         AlphaRank(moveList[moveNum], 4);
4447         SendToProgram(moveList[moveNum], cps);
4448         AlphaRank(moveList[moveNum], 4); // and back
4449       } else
4450       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4451        * the engine. It would be nice to have a better way to identify castle 
4452        * moves here. */
4453       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4454                                                                          && cps->useOOCastle) {
4455         int fromX = moveList[moveNum][0] - AAA; 
4456         int fromY = moveList[moveNum][1] - ONE;
4457         int toX = moveList[moveNum][2] - AAA; 
4458         int toY = moveList[moveNum][3] - ONE;
4459         if((boards[moveNum][fromY][fromX] == WhiteKing 
4460             && boards[moveNum][toY][toX] == WhiteRook)
4461            || (boards[moveNum][fromY][fromX] == BlackKing 
4462                && boards[moveNum][toY][toX] == BlackRook)) {
4463           if(toX > fromX) SendToProgram("O-O\n", cps);
4464           else SendToProgram("O-O-O\n", cps);
4465         }
4466         else SendToProgram(moveList[moveNum], cps);
4467       }
4468       else SendToProgram(moveList[moveNum], cps);
4469       /* End of additions by Tord */
4470     }
4471
4472     /* [HGM] setting up the opening has brought engine in force mode! */
4473     /*       Send 'go' if we are in a mode where machine should play. */
4474     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4475         (gameMode == TwoMachinesPlay   ||
4476 #ifdef ZIPPY
4477          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4478 #endif
4479          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4480         SendToProgram("go\n", cps);
4481   if (appData.debugMode) {
4482     fprintf(debugFP, "(extra)\n");
4483   }
4484     }
4485     setboardSpoiledMachineBlack = 0;
4486 }
4487
4488 void
4489 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4490      ChessMove moveType;
4491      int fromX, fromY, toX, toY;
4492 {
4493     char user_move[MSG_SIZ];
4494
4495     switch (moveType) {
4496       default:
4497         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4498                 (int)moveType, fromX, fromY, toX, toY);
4499         DisplayError(user_move + strlen("say "), 0);
4500         break;
4501       case WhiteKingSideCastle:
4502       case BlackKingSideCastle:
4503       case WhiteQueenSideCastleWild:
4504       case BlackQueenSideCastleWild:
4505       /* PUSH Fabien */
4506       case WhiteHSideCastleFR:
4507       case BlackHSideCastleFR:
4508       /* POP Fabien */
4509         sprintf(user_move, "o-o\n");
4510         break;
4511       case WhiteQueenSideCastle:
4512       case BlackQueenSideCastle:
4513       case WhiteKingSideCastleWild:
4514       case BlackKingSideCastleWild:
4515       /* PUSH Fabien */
4516       case WhiteASideCastleFR:
4517       case BlackASideCastleFR:
4518       /* POP Fabien */
4519         sprintf(user_move, "o-o-o\n");
4520         break;
4521       case WhitePromotionQueen:
4522       case BlackPromotionQueen:
4523       case WhitePromotionRook:
4524       case BlackPromotionRook:
4525       case WhitePromotionBishop:
4526       case BlackPromotionBishop:
4527       case WhitePromotionKnight:
4528       case BlackPromotionKnight:
4529       case WhitePromotionKing:
4530       case BlackPromotionKing:
4531       case WhitePromotionChancellor:
4532       case BlackPromotionChancellor:
4533       case WhitePromotionArchbishop:
4534       case BlackPromotionArchbishop:
4535         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4536             sprintf(user_move, "%c%c%c%c=%c\n",
4537                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4538                 PieceToChar(WhiteFerz));
4539         else if(gameInfo.variant == VariantGreat)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteMan));
4543         else
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(PromoPiece(moveType)));
4547         break;
4548       case WhiteDrop:
4549       case BlackDrop:
4550         sprintf(user_move, "%c@%c%c\n",
4551                 ToUpper(PieceToChar((ChessSquare) fromX)),
4552                 AAA + toX, ONE + toY);
4553         break;
4554       case NormalMove:
4555       case WhiteCapturesEnPassant:
4556       case BlackCapturesEnPassant:
4557       case IllegalMove:  /* could be a variant we don't quite understand */
4558         sprintf(user_move, "%c%c%c%c\n",
4559                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4560         break;
4561     }
4562     SendToICS(user_move);
4563     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4564         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4565 }
4566
4567 void
4568 UploadGameEvent()
4569 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4570     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4571     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4572     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4573         DisplayError("You cannot do this while you are playing or observing", 0);
4574         return;
4575     }
4576     if(gameMode != IcsExamining) { // is this ever not the case?
4577         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4578
4579         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4580             sprintf(command, "match %s", ics_handle);
4581         } else { // on FICS we must first go to general examine mode
4582             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4583         }
4584         if(gameInfo.variant != VariantNormal) {
4585             // try figure out wild number, as xboard names are not always valid on ICS
4586             for(i=1; i<=36; i++) {
4587                 sprintf(buf, "wild/%d", i);
4588                 if(StringToVariant(buf) == gameInfo.variant) break;
4589             }
4590             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4591             else if(i == 22) sprintf(buf, "%s fr\n", command);
4592             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4593         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4594         SendToICS(ics_prefix);
4595         SendToICS(buf);
4596         if(startedFromSetupPosition || backwardMostMove != 0) {
4597           fen = PositionToFEN(backwardMostMove, NULL);
4598           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4599             sprintf(buf, "loadfen %s\n", fen);
4600             SendToICS(buf);
4601           } else { // FICS: everything has to set by separate bsetup commands
4602             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4603             sprintf(buf, "bsetup fen %s\n", fen);
4604             SendToICS(buf);
4605             if(!WhiteOnMove(backwardMostMove)) {
4606                 SendToICS("bsetup tomove black\n");
4607             }
4608             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4609             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4610             SendToICS(buf);
4611             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4612             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4613             SendToICS(buf);
4614             i = boards[backwardMostMove][EP_STATUS];
4615             if(i >= 0) { // set e.p.
4616                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4617                 SendToICS(buf);
4618             }
4619             bsetup++;
4620           }
4621         }
4622       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4623             SendToICS("bsetup done\n"); // switch to normal examining.
4624     }
4625     for(i = backwardMostMove; i<last; i++) {
4626         char buf[20];
4627         sprintf(buf, "%s\n", parseList[i]);
4628         SendToICS(buf);
4629     }
4630     SendToICS(ics_prefix);
4631     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4632 }
4633
4634 void
4635 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4636      int rf, ff, rt, ft;
4637      char promoChar;
4638      char move[7];
4639 {
4640     if (rf == DROP_RANK) {
4641         sprintf(move, "%c@%c%c\n",
4642                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4643     } else {
4644         if (promoChar == 'x' || promoChar == NULLCHAR) {
4645             sprintf(move, "%c%c%c%c\n",
4646                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4647         } else {
4648             sprintf(move, "%c%c%c%c%c\n",
4649                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4650         }
4651     }
4652 }
4653
4654 void
4655 ProcessICSInitScript(f)
4656      FILE *f;
4657 {
4658     char buf[MSG_SIZ];
4659
4660     while (fgets(buf, MSG_SIZ, f)) {
4661         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4662     }
4663
4664     fclose(f);
4665 }
4666
4667
4668 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4669 void
4670 AlphaRank(char *move, int n)
4671 {
4672 //    char *p = move, c; int x, y;
4673
4674     if (appData.debugMode) {
4675         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4676     }
4677
4678     if(move[1]=='*' && 
4679        move[2]>='0' && move[2]<='9' &&
4680        move[3]>='a' && move[3]<='x'    ) {
4681         move[1] = '@';
4682         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4683         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4684     } else
4685     if(move[0]>='0' && move[0]<='9' &&
4686        move[1]>='a' && move[1]<='x' &&
4687        move[2]>='0' && move[2]<='9' &&
4688        move[3]>='a' && move[3]<='x'    ) {
4689         /* input move, Shogi -> normal */
4690         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4691         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4692         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4693         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4694     } else
4695     if(move[1]=='@' &&
4696        move[3]>='0' && move[3]<='9' &&
4697        move[2]>='a' && move[2]<='x'    ) {
4698         move[1] = '*';
4699         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4700         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4701     } else
4702     if(
4703        move[0]>='a' && move[0]<='x' &&
4704        move[3]>='0' && move[3]<='9' &&
4705        move[2]>='a' && move[2]<='x'    ) {
4706          /* output move, normal -> Shogi */
4707         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4708         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4709         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4710         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4711         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4712     }
4713     if (appData.debugMode) {
4714         fprintf(debugFP, "   out = '%s'\n", move);
4715     }
4716 }
4717
4718 /* Parser for moves from gnuchess, ICS, or user typein box */
4719 Boolean
4720 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4721      char *move;
4722      int moveNum;
4723      ChessMove *moveType;
4724      int *fromX, *fromY, *toX, *toY;
4725      char *promoChar;
4726 {       
4727     if (appData.debugMode) {
4728         fprintf(debugFP, "move to parse: %s\n", move);
4729     }
4730     *moveType = yylexstr(moveNum, move);
4731
4732     switch (*moveType) {
4733       case WhitePromotionChancellor:
4734       case BlackPromotionChancellor:
4735       case WhitePromotionArchbishop:
4736       case BlackPromotionArchbishop:
4737       case WhitePromotionQueen:
4738       case BlackPromotionQueen:
4739       case WhitePromotionRook:
4740       case BlackPromotionRook:
4741       case WhitePromotionBishop:
4742       case BlackPromotionBishop:
4743       case WhitePromotionKnight:
4744       case BlackPromotionKnight:
4745       case WhitePromotionKing:
4746       case BlackPromotionKing:
4747       case NormalMove:
4748       case WhiteCapturesEnPassant:
4749       case BlackCapturesEnPassant:
4750       case WhiteKingSideCastle:
4751       case WhiteQueenSideCastle:
4752       case BlackKingSideCastle:
4753       case BlackQueenSideCastle:
4754       case WhiteKingSideCastleWild:
4755       case WhiteQueenSideCastleWild:
4756       case BlackKingSideCastleWild:
4757       case BlackQueenSideCastleWild:
4758       /* Code added by Tord: */
4759       case WhiteHSideCastleFR:
4760       case WhiteASideCastleFR:
4761       case BlackHSideCastleFR:
4762       case BlackASideCastleFR:
4763       /* End of code added by Tord */
4764       case IllegalMove:         /* bug or odd chess variant */
4765         *fromX = currentMoveString[0] - AAA;
4766         *fromY = currentMoveString[1] - ONE;
4767         *toX = currentMoveString[2] - AAA;
4768         *toY = currentMoveString[3] - ONE;
4769         *promoChar = currentMoveString[4];
4770         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4771             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4772     if (appData.debugMode) {
4773         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4774     }
4775             *fromX = *fromY = *toX = *toY = 0;
4776             return FALSE;
4777         }
4778         if (appData.testLegality) {
4779           return (*moveType != IllegalMove);
4780         } else {
4781           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4782                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4783         }
4784
4785       case WhiteDrop:
4786       case BlackDrop:
4787         *fromX = *moveType == WhiteDrop ?
4788           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4789           (int) CharToPiece(ToLower(currentMoveString[0]));
4790         *fromY = DROP_RANK;
4791         *toX = currentMoveString[2] - AAA;
4792         *toY = currentMoveString[3] - ONE;
4793         *promoChar = NULLCHAR;
4794         return TRUE;
4795
4796       case AmbiguousMove:
4797       case ImpossibleMove:
4798       case (ChessMove) 0:       /* end of file */
4799       case ElapsedTime:
4800       case Comment:
4801       case PGNTag:
4802       case NAG:
4803       case WhiteWins:
4804       case BlackWins:
4805       case GameIsDrawn:
4806       default:
4807     if (appData.debugMode) {
4808         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4809     }
4810         /* bug? */
4811         *fromX = *fromY = *toX = *toY = 0;
4812         *promoChar = NULLCHAR;
4813         return FALSE;
4814     }
4815 }
4816
4817
4818 void
4819 ParsePV(char *pv)
4820 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4821   int fromX, fromY, toX, toY; char promoChar;
4822   ChessMove moveType;
4823   Boolean valid;
4824   int nr = 0;
4825
4826   endPV = forwardMostMove;
4827   do {
4828     while(*pv == ' ') pv++;
4829     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4830     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4831 if(appData.debugMode){
4832 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4833 }
4834     if(!valid && nr == 0 &&
4835        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4836         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4837         // Hande case where played move is different from leading PV move
4838         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4839         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4840         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4841         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4842           endPV += 2; // if position different, keep this
4843           moveList[endPV-1][0] = fromX + AAA;
4844           moveList[endPV-1][1] = fromY + ONE;
4845           moveList[endPV-1][2] = toX + AAA;
4846           moveList[endPV-1][3] = toY + ONE;
4847           parseList[endPV-1][0] = NULLCHAR;
4848           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4849         }
4850       }
4851     }
4852     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4853     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4854     nr++;
4855     if(endPV+1 > framePtr) break; // no space, truncate
4856     if(!valid) break;
4857     endPV++;
4858     CopyBoard(boards[endPV], boards[endPV-1]);
4859     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4860     moveList[endPV-1][0] = fromX + AAA;
4861     moveList[endPV-1][1] = fromY + ONE;
4862     moveList[endPV-1][2] = toX + AAA;
4863     moveList[endPV-1][3] = toY + ONE;
4864     parseList[endPV-1][0] = NULLCHAR;
4865   } while(valid);
4866   currentMove = endPV;
4867   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4868   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4869                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4870   DrawPosition(TRUE, boards[currentMove]);
4871 }
4872
4873 static int lastX, lastY;
4874
4875 Boolean
4876 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4877 {
4878         int startPV;
4879
4880         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4881         lastX = x; lastY = y;
4882         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4883         startPV = index;
4884       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4885       index = startPV;
4886         while(buf[index] && buf[index] != '\n') index++;
4887         buf[index] = 0;
4888         ParsePV(buf+startPV);
4889         *start = startPV; *end = index-1;
4890         return TRUE;
4891 }
4892
4893 Boolean
4894 LoadPV(int x, int y)
4895 { // called on right mouse click to load PV
4896   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4897   lastX = x; lastY = y;
4898   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4899   return TRUE;
4900 }
4901
4902 void
4903 UnLoadPV()
4904 {
4905   if(endPV < 0) return;
4906   endPV = -1;
4907   currentMove = forwardMostMove;
4908   ClearPremoveHighlights();
4909   DrawPosition(TRUE, boards[currentMove]);
4910 }
4911
4912 void
4913 MovePV(int x, int y, int h)
4914 { // step through PV based on mouse coordinates (called on mouse move)
4915   int margin = h>>3, step = 0;
4916
4917   if(endPV < 0) return;
4918   // we must somehow check if right button is still down (might be released off board!)
4919   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4920   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4921   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4922   if(!step) return;
4923   lastX = x; lastY = y;
4924   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4925   currentMove += step;
4926   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4927   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4928                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4929   DrawPosition(FALSE, boards[currentMove]);
4930 }
4931
4932
4933 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4934 // All positions will have equal probability, but the current method will not provide a unique
4935 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4936 #define DARK 1
4937 #define LITE 2
4938 #define ANY 3
4939
4940 int squaresLeft[4];
4941 int piecesLeft[(int)BlackPawn];
4942 int seed, nrOfShuffles;
4943
4944 void GetPositionNumber()
4945 {       // sets global variable seed
4946         int i;
4947
4948         seed = appData.defaultFrcPosition;
4949         if(seed < 0) { // randomize based on time for negative FRC position numbers
4950                 for(i=0; i<50; i++) seed += random();
4951                 seed = random() ^ random() >> 8 ^ random() << 8;
4952                 if(seed<0) seed = -seed;
4953         }
4954 }
4955
4956 int put(Board board, int pieceType, int rank, int n, int shade)
4957 // put the piece on the (n-1)-th empty squares of the given shade
4958 {
4959         int i;
4960
4961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4962                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4963                         board[rank][i] = (ChessSquare) pieceType;
4964                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4965                         squaresLeft[ANY]--;
4966                         piecesLeft[pieceType]--; 
4967                         return i;
4968                 }
4969         }
4970         return -1;
4971 }
4972
4973
4974 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4975 // calculate where the next piece goes, (any empty square), and put it there
4976 {
4977         int i;
4978
4979         i = seed % squaresLeft[shade];
4980         nrOfShuffles *= squaresLeft[shade];
4981         seed /= squaresLeft[shade];
4982         put(board, pieceType, rank, i, shade);
4983 }
4984
4985 void AddTwoPieces(Board board, int pieceType, int rank)
4986 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4987 {
4988         int i, n=squaresLeft[ANY], j=n-1, k;
4989
4990         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4991         i = seed % k;  // pick one
4992         nrOfShuffles *= k;
4993         seed /= k;
4994         while(i >= j) i -= j--;
4995         j = n - 1 - j; i += j;
4996         put(board, pieceType, rank, j, ANY);
4997         put(board, pieceType, rank, i, ANY);
4998 }
4999
5000 void SetUpShuffle(Board board, int number)
5001 {
5002         int i, p, first=1;
5003
5004         GetPositionNumber(); nrOfShuffles = 1;
5005
5006         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5007         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5008         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5009
5010         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5011
5012         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5013             p = (int) board[0][i];
5014             if(p < (int) BlackPawn) piecesLeft[p] ++;
5015             board[0][i] = EmptySquare;
5016         }
5017
5018         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5019             // shuffles restricted to allow normal castling put KRR first
5020             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5021                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5022             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5023                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5024             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5025                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5026             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5027                 put(board, WhiteRook, 0, 0, ANY);
5028             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5029         }
5030
5031         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5032             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5033             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5034                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5035                 while(piecesLeft[p] >= 2) {
5036                     AddOnePiece(board, p, 0, LITE);
5037                     AddOnePiece(board, p, 0, DARK);
5038                 }
5039                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5040             }
5041
5042         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5043             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5044             // but we leave King and Rooks for last, to possibly obey FRC restriction
5045             if(p == (int)WhiteRook) continue;
5046             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5047             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5048         }
5049
5050         // now everything is placed, except perhaps King (Unicorn) and Rooks
5051
5052         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5053             // Last King gets castling rights
5054             while(piecesLeft[(int)WhiteUnicorn]) {
5055                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5056                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5057             }
5058
5059             while(piecesLeft[(int)WhiteKing]) {
5060                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5061                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5062             }
5063
5064
5065         } else {
5066             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5067             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5068         }
5069
5070         // Only Rooks can be left; simply place them all
5071         while(piecesLeft[(int)WhiteRook]) {
5072                 i = put(board, WhiteRook, 0, 0, ANY);
5073                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5074                         if(first) {
5075                                 first=0;
5076                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5077                         }
5078                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5079                 }
5080         }
5081         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5082             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5083         }
5084
5085         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5086 }
5087
5088 int SetCharTable( char *table, const char * map )
5089 /* [HGM] moved here from winboard.c because of its general usefulness */
5090 /*       Basically a safe strcpy that uses the last character as King */
5091 {
5092     int result = FALSE; int NrPieces;
5093
5094     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5095                     && NrPieces >= 12 && !(NrPieces&1)) {
5096         int i; /* [HGM] Accept even length from 12 to 34 */
5097
5098         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5099         for( i=0; i<NrPieces/2-1; i++ ) {
5100             table[i] = map[i];
5101             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5102         }
5103         table[(int) WhiteKing]  = map[NrPieces/2-1];
5104         table[(int) BlackKing]  = map[NrPieces-1];
5105
5106         result = TRUE;
5107     }
5108
5109     return result;
5110 }
5111
5112 void Prelude(Board board)
5113 {       // [HGM] superchess: random selection of exo-pieces
5114         int i, j, k; ChessSquare p; 
5115         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5116
5117         GetPositionNumber(); // use FRC position number
5118
5119         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5120             SetCharTable(pieceToChar, appData.pieceToCharTable);
5121             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5122                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5123         }
5124
5125         j = seed%4;                 seed /= 4; 
5126         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5127         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5128         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5129         j = seed%3 + (seed%3 >= j); seed /= 3; 
5130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5132         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5133         j = seed%3;                 seed /= 3; 
5134         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5136         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5137         j = seed%2 + (seed%2 >= j); seed /= 2; 
5138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5140         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5141         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5142         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5143         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5144         put(board, exoPieces[0],    0, 0, ANY);
5145         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5146 }
5147
5148 void
5149 InitPosition(redraw)
5150      int redraw;
5151 {
5152     ChessSquare (* pieces)[BOARD_FILES];
5153     int i, j, pawnRow, overrule,
5154     oldx = gameInfo.boardWidth,
5155     oldy = gameInfo.boardHeight,
5156     oldh = gameInfo.holdingsWidth,
5157     oldv = gameInfo.variant;
5158
5159     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5160
5161     /* [AS] Initialize pv info list [HGM] and game status */
5162     {
5163         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5164             pvInfoList[i].depth = 0;
5165             boards[i][EP_STATUS] = EP_NONE;
5166             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5167         }
5168
5169         initialRulePlies = 0; /* 50-move counter start */
5170
5171         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5172         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5173     }
5174
5175     
5176     /* [HGM] logic here is completely changed. In stead of full positions */
5177     /* the initialized data only consist of the two backranks. The switch */
5178     /* selects which one we will use, which is than copied to the Board   */
5179     /* initialPosition, which for the rest is initialized by Pawns and    */
5180     /* empty squares. This initial position is then copied to boards[0],  */
5181     /* possibly after shuffling, so that it remains available.            */
5182
5183     gameInfo.holdingsWidth = 0; /* default board sizes */
5184     gameInfo.boardWidth    = 8;
5185     gameInfo.boardHeight   = 8;
5186     gameInfo.holdingsSize  = 0;
5187     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5188     for(i=0; i<BOARD_FILES-2; i++)
5189       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5190     initialPosition[EP_STATUS] = EP_NONE;
5191     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5192
5193     switch (gameInfo.variant) {
5194     case VariantFischeRandom:
5195       shuffleOpenings = TRUE;
5196     default:
5197       pieces = FIDEArray;
5198       break;
5199     case VariantShatranj:
5200       pieces = ShatranjArray;
5201       nrCastlingRights = 0;
5202       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5203       break;
5204     case VariantMakruk:
5205       pieces = makrukArray;
5206       nrCastlingRights = 0;
5207       startedFromSetupPosition = TRUE;
5208       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5209       break;
5210     case VariantTwoKings:
5211       pieces = twoKingsArray;
5212       break;
5213     case VariantCapaRandom:
5214       shuffleOpenings = TRUE;
5215     case VariantCapablanca:
5216       pieces = CapablancaArray;
5217       gameInfo.boardWidth = 10;
5218       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5219       break;
5220     case VariantGothic:
5221       pieces = GothicArray;
5222       gameInfo.boardWidth = 10;
5223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5224       break;
5225     case VariantJanus:
5226       pieces = JanusArray;
5227       gameInfo.boardWidth = 10;
5228       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5229       nrCastlingRights = 6;
5230         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5231         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5232         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5233         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5234         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5235         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5236       break;
5237     case VariantFalcon:
5238       pieces = FalconArray;
5239       gameInfo.boardWidth = 10;
5240       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5241       break;
5242     case VariantXiangqi:
5243       pieces = XiangqiArray;
5244       gameInfo.boardWidth  = 9;
5245       gameInfo.boardHeight = 10;
5246       nrCastlingRights = 0;
5247       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5248       break;
5249     case VariantShogi:
5250       pieces = ShogiArray;
5251       gameInfo.boardWidth  = 9;
5252       gameInfo.boardHeight = 9;
5253       gameInfo.holdingsSize = 7;
5254       nrCastlingRights = 0;
5255       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5256       break;
5257     case VariantCourier:
5258       pieces = CourierArray;
5259       gameInfo.boardWidth  = 12;
5260       nrCastlingRights = 0;
5261       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5262       break;
5263     case VariantKnightmate:
5264       pieces = KnightmateArray;
5265       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5266       break;
5267     case VariantFairy:
5268       pieces = fairyArray;
5269       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5270       break;
5271     case VariantGreat:
5272       pieces = GreatArray;
5273       gameInfo.boardWidth = 10;
5274       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5275       gameInfo.holdingsSize = 8;
5276       break;
5277     case VariantSuper:
5278       pieces = FIDEArray;
5279       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5280       gameInfo.holdingsSize = 8;
5281       startedFromSetupPosition = TRUE;
5282       break;
5283     case VariantCrazyhouse:
5284     case VariantBughouse:
5285       pieces = FIDEArray;
5286       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5287       gameInfo.holdingsSize = 5;
5288       break;
5289     case VariantWildCastle:
5290       pieces = FIDEArray;
5291       /* !!?shuffle with kings guaranteed to be on d or e file */
5292       shuffleOpenings = 1;
5293       break;
5294     case VariantNoCastle:
5295       pieces = FIDEArray;
5296       nrCastlingRights = 0;
5297       /* !!?unconstrained back-rank shuffle */
5298       shuffleOpenings = 1;
5299       break;
5300     }
5301
5302     overrule = 0;
5303     if(appData.NrFiles >= 0) {
5304         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5305         gameInfo.boardWidth = appData.NrFiles;
5306     }
5307     if(appData.NrRanks >= 0) {
5308         gameInfo.boardHeight = appData.NrRanks;
5309     }
5310     if(appData.holdingsSize >= 0) {
5311         i = appData.holdingsSize;
5312         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5313         gameInfo.holdingsSize = i;
5314     }
5315     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5316     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5317         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5318
5319     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5320     if(pawnRow < 1) pawnRow = 1;
5321     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5322
5323     /* User pieceToChar list overrules defaults */
5324     if(appData.pieceToCharTable != NULL)
5325         SetCharTable(pieceToChar, appData.pieceToCharTable);
5326
5327     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5328
5329         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5330             s = (ChessSquare) 0; /* account holding counts in guard band */
5331         for( i=0; i<BOARD_HEIGHT; i++ )
5332             initialPosition[i][j] = s;
5333
5334         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5335         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5336         initialPosition[pawnRow][j] = WhitePawn;
5337         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5338         if(gameInfo.variant == VariantXiangqi) {
5339             if(j&1) {
5340                 initialPosition[pawnRow][j] = 
5341                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5342                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5343                    initialPosition[2][j] = WhiteCannon;
5344                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5345                 }
5346             }
5347         }
5348         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5349     }
5350     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5351
5352             j=BOARD_LEFT+1;
5353             initialPosition[1][j] = WhiteBishop;
5354             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5355             j=BOARD_RGHT-2;
5356             initialPosition[1][j] = WhiteRook;
5357             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5358     }
5359
5360     if( nrCastlingRights == -1) {
5361         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5362         /*       This sets default castling rights from none to normal corners   */
5363         /* Variants with other castling rights must set them themselves above    */
5364         nrCastlingRights = 6;
5365        
5366         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5367         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5368         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5369         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5370         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5371         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5372      }
5373
5374      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5375      if(gameInfo.variant == VariantGreat) { // promotion commoners
5376         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5377         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5378         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5379         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5380      }
5381   if (appData.debugMode) {
5382     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5383   }
5384     if(shuffleOpenings) {
5385         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5386         startedFromSetupPosition = TRUE;
5387     }
5388     if(startedFromPositionFile) {
5389       /* [HGM] loadPos: use PositionFile for every new game */
5390       CopyBoard(initialPosition, filePosition);
5391       for(i=0; i<nrCastlingRights; i++)
5392           initialRights[i] = filePosition[CASTLING][i];
5393       startedFromSetupPosition = TRUE;
5394     }
5395
5396     CopyBoard(boards[0], initialPosition);
5397
5398     if(oldx != gameInfo.boardWidth ||
5399        oldy != gameInfo.boardHeight ||
5400        oldh != gameInfo.holdingsWidth
5401 #ifdef GOTHIC
5402        || oldv == VariantGothic ||        // For licensing popups
5403        gameInfo.variant == VariantGothic
5404 #endif
5405 #ifdef FALCON
5406        || oldv == VariantFalcon ||
5407        gameInfo.variant == VariantFalcon
5408 #endif
5409                                          )
5410             InitDrawingSizes(-2 ,0);
5411
5412     if (redraw)
5413       DrawPosition(TRUE, boards[currentMove]);
5414 }
5415
5416 void
5417 SendBoard(cps, moveNum)
5418      ChessProgramState *cps;
5419      int moveNum;
5420 {
5421     char message[MSG_SIZ];
5422     
5423     if (cps->useSetboard) {
5424       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5425       sprintf(message, "setboard %s\n", fen);
5426       SendToProgram(message, cps);
5427       free(fen);
5428
5429     } else {
5430       ChessSquare *bp;
5431       int i, j;
5432       /* Kludge to set black to move, avoiding the troublesome and now
5433        * deprecated "black" command.
5434        */
5435       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5436
5437       SendToProgram("edit\n", cps);
5438       SendToProgram("#\n", cps);
5439       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5440         bp = &boards[moveNum][i][BOARD_LEFT];
5441         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5442           if ((int) *bp < (int) BlackPawn) {
5443             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5444                     AAA + j, ONE + i);
5445             if(message[0] == '+' || message[0] == '~') {
5446                 sprintf(message, "%c%c%c+\n",
5447                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5448                         AAA + j, ONE + i);
5449             }
5450             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5451                 message[1] = BOARD_RGHT   - 1 - j + '1';
5452                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5453             }
5454             SendToProgram(message, cps);
5455           }
5456         }
5457       }
5458     
5459       SendToProgram("c\n", cps);
5460       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5461         bp = &boards[moveNum][i][BOARD_LEFT];
5462         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5463           if (((int) *bp != (int) EmptySquare)
5464               && ((int) *bp >= (int) BlackPawn)) {
5465             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5466                     AAA + j, ONE + i);
5467             if(message[0] == '+' || message[0] == '~') {
5468                 sprintf(message, "%c%c%c+\n",
5469                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5470                         AAA + j, ONE + i);
5471             }
5472             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5473                 message[1] = BOARD_RGHT   - 1 - j + '1';
5474                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5475             }
5476             SendToProgram(message, cps);
5477           }
5478         }
5479       }
5480     
5481       SendToProgram(".\n", cps);
5482     }
5483     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5484 }
5485
5486 static int autoQueen; // [HGM] oneclick
5487
5488 int
5489 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5490 {
5491     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5492     /* [HGM] add Shogi promotions */
5493     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5494     ChessSquare piece;
5495     ChessMove moveType;
5496     Boolean premove;
5497
5498     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5499     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5500
5501     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5502       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5503         return FALSE;
5504
5505     piece = boards[currentMove][fromY][fromX];
5506     if(gameInfo.variant == VariantShogi) {
5507         promotionZoneSize = 3;
5508         highestPromotingPiece = (int)WhiteFerz;
5509     } else if(gameInfo.variant == VariantMakruk) {
5510         promotionZoneSize = 3;
5511     }
5512
5513     // next weed out all moves that do not touch the promotion zone at all
5514     if((int)piece >= BlackPawn) {
5515         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5516              return FALSE;
5517         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5518     } else {
5519         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5520            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5521     }
5522
5523     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5524
5525     // weed out mandatory Shogi promotions
5526     if(gameInfo.variant == VariantShogi) {
5527         if(piece >= BlackPawn) {
5528             if(toY == 0 && piece == BlackPawn ||
5529                toY == 0 && piece == BlackQueen ||
5530                toY <= 1 && piece == BlackKnight) {
5531                 *promoChoice = '+';
5532                 return FALSE;
5533             }
5534         } else {
5535             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5536                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5537                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5538                 *promoChoice = '+';
5539                 return FALSE;
5540             }
5541         }
5542     }
5543
5544     // weed out obviously illegal Pawn moves
5545     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5546         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5547         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5548         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5549         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5550         // note we are not allowed to test for valid (non-)capture, due to premove
5551     }
5552
5553     // we either have a choice what to promote to, or (in Shogi) whether to promote
5554     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5555         *promoChoice = PieceToChar(BlackFerz);  // no choice
5556         return FALSE;
5557     }
5558     if(autoQueen) { // predetermined
5559         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5560              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5561         else *promoChoice = PieceToChar(BlackQueen);
5562         return FALSE;
5563     }
5564
5565     // suppress promotion popup on illegal moves that are not premoves
5566     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5567               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5568     if(appData.testLegality && !premove) {
5569         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5570                         fromY, fromX, toY, toX, NULLCHAR);
5571         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5572            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5573             return FALSE;
5574     }
5575
5576     return TRUE;
5577 }
5578
5579 int
5580 InPalace(row, column)
5581      int row, column;
5582 {   /* [HGM] for Xiangqi */
5583     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5584          column < (BOARD_WIDTH + 4)/2 &&
5585          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5586     return FALSE;
5587 }
5588
5589 int
5590 PieceForSquare (x, y)
5591      int x;
5592      int y;
5593 {
5594   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5595      return -1;
5596   else
5597      return boards[currentMove][y][x];
5598 }
5599
5600 int
5601 OKToStartUserMove(x, y)
5602      int x, y;
5603 {
5604     ChessSquare from_piece;
5605     int white_piece;
5606
5607     if (matchMode) return FALSE;
5608     if (gameMode == EditPosition) return TRUE;
5609
5610     if (x >= 0 && y >= 0)
5611       from_piece = boards[currentMove][y][x];
5612     else
5613       from_piece = EmptySquare;
5614
5615     if (from_piece == EmptySquare) return FALSE;
5616
5617     white_piece = (int)from_piece >= (int)WhitePawn &&
5618       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5619
5620     switch (gameMode) {
5621       case PlayFromGameFile:
5622       case AnalyzeFile:
5623       case TwoMachinesPlay:
5624       case EndOfGame:
5625         return FALSE;
5626
5627       case IcsObserving:
5628       case IcsIdle:
5629         return FALSE;
5630
5631       case MachinePlaysWhite:
5632       case IcsPlayingBlack:
5633         if (appData.zippyPlay) return FALSE;
5634         if (white_piece) {
5635             DisplayMoveError(_("You are playing Black"));
5636             return FALSE;
5637         }
5638         break;
5639
5640       case MachinePlaysBlack:
5641       case IcsPlayingWhite:
5642         if (appData.zippyPlay) return FALSE;
5643         if (!white_piece) {
5644             DisplayMoveError(_("You are playing White"));
5645             return FALSE;
5646         }
5647         break;
5648
5649       case EditGame:
5650         if (!white_piece && WhiteOnMove(currentMove)) {
5651             DisplayMoveError(_("It is White's turn"));
5652             return FALSE;
5653         }           
5654         if (white_piece && !WhiteOnMove(currentMove)) {
5655             DisplayMoveError(_("It is Black's turn"));
5656             return FALSE;
5657         }           
5658         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5659             /* Editing correspondence game history */
5660             /* Could disallow this or prompt for confirmation */
5661             cmailOldMove = -1;
5662         }
5663         break;
5664
5665       case BeginningOfGame:
5666         if (appData.icsActive) return FALSE;
5667         if (!appData.noChessProgram) {
5668             if (!white_piece) {
5669                 DisplayMoveError(_("You are playing White"));
5670                 return FALSE;
5671             }
5672         }
5673         break;
5674         
5675       case Training:
5676         if (!white_piece && WhiteOnMove(currentMove)) {
5677             DisplayMoveError(_("It is White's turn"));
5678             return FALSE;
5679         }           
5680         if (white_piece && !WhiteOnMove(currentMove)) {
5681             DisplayMoveError(_("It is Black's turn"));
5682             return FALSE;
5683         }           
5684         break;
5685
5686       default:
5687       case IcsExamining:
5688         break;
5689     }
5690     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5691         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5692         && gameMode != AnalyzeFile && gameMode != Training) {
5693         DisplayMoveError(_("Displayed position is not current"));
5694         return FALSE;
5695     }
5696     return TRUE;
5697 }
5698
5699 Boolean
5700 OnlyMove(int *x, int *y, Boolean captures) {
5701     DisambiguateClosure cl;
5702     if (appData.zippyPlay) return FALSE;
5703     switch(gameMode) {
5704       case MachinePlaysBlack:
5705       case IcsPlayingWhite:
5706       case BeginningOfGame:
5707         if(!WhiteOnMove(currentMove)) return FALSE;
5708         break;
5709       case MachinePlaysWhite:
5710       case IcsPlayingBlack:
5711         if(WhiteOnMove(currentMove)) return FALSE;
5712         break;
5713       default:
5714         return FALSE;
5715     }
5716     cl.pieceIn = EmptySquare; 
5717     cl.rfIn = *y;
5718     cl.ffIn = *x;
5719     cl.rtIn = -1;
5720     cl.ftIn = -1;
5721     cl.promoCharIn = NULLCHAR;
5722     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5723     if( cl.kind == NormalMove ||
5724         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5725         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5726         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5727         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5728       fromX = cl.ff;
5729       fromY = cl.rf;
5730       *x = cl.ft;
5731       *y = cl.rt;
5732       return TRUE;
5733     }
5734     if(cl.kind != ImpossibleMove) return FALSE;
5735     cl.pieceIn = EmptySquare;
5736     cl.rfIn = -1;
5737     cl.ffIn = -1;
5738     cl.rtIn = *y;
5739     cl.ftIn = *x;
5740     cl.promoCharIn = NULLCHAR;
5741     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5742     if( cl.kind == NormalMove ||
5743         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5744         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5745         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5746         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5747       fromX = cl.ff;
5748       fromY = cl.rf;
5749       *x = cl.ft;
5750       *y = cl.rt;
5751       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5752       return TRUE;
5753     }
5754     return FALSE;
5755 }
5756
5757 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5758 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5759 int lastLoadGameUseList = FALSE;
5760 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5761 ChessMove lastLoadGameStart = (ChessMove) 0;
5762
5763 ChessMove
5764 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5765      int fromX, fromY, toX, toY;
5766      int promoChar;
5767      Boolean captureOwn;
5768 {
5769     ChessMove moveType;
5770     ChessSquare pdown, pup;
5771
5772     /* Check if the user is playing in turn.  This is complicated because we
5773        let the user "pick up" a piece before it is his turn.  So the piece he
5774        tried to pick up may have been captured by the time he puts it down!
5775        Therefore we use the color the user is supposed to be playing in this
5776        test, not the color of the piece that is currently on the starting
5777        square---except in EditGame mode, where the user is playing both
5778        sides; fortunately there the capture race can't happen.  (It can
5779        now happen in IcsExamining mode, but that's just too bad.  The user
5780        will get a somewhat confusing message in that case.)
5781        */
5782
5783     switch (gameMode) {
5784       case PlayFromGameFile:
5785       case AnalyzeFile:
5786       case TwoMachinesPlay:
5787       case EndOfGame:
5788       case IcsObserving:
5789       case IcsIdle:
5790         /* We switched into a game mode where moves are not accepted,
5791            perhaps while the mouse button was down. */
5792         return ImpossibleMove;
5793
5794       case MachinePlaysWhite:
5795         /* User is moving for Black */
5796         if (WhiteOnMove(currentMove)) {
5797             DisplayMoveError(_("It is White's turn"));
5798             return ImpossibleMove;
5799         }
5800         break;
5801
5802       case MachinePlaysBlack:
5803         /* User is moving for White */
5804         if (!WhiteOnMove(currentMove)) {
5805             DisplayMoveError(_("It is Black's turn"));
5806             return ImpossibleMove;
5807         }
5808         break;
5809
5810       case EditGame:
5811       case IcsExamining:
5812       case BeginningOfGame:
5813       case AnalyzeMode:
5814       case Training:
5815         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5816             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5817             /* User is moving for Black */
5818             if (WhiteOnMove(currentMove)) {
5819                 DisplayMoveError(_("It is White's turn"));
5820                 return ImpossibleMove;
5821             }
5822         } else {
5823             /* User is moving for White */
5824             if (!WhiteOnMove(currentMove)) {
5825                 DisplayMoveError(_("It is Black's turn"));
5826                 return ImpossibleMove;
5827             }
5828         }
5829         break;
5830
5831       case IcsPlayingBlack:
5832         /* User is moving for Black */
5833         if (WhiteOnMove(currentMove)) {
5834             if (!appData.premove) {
5835                 DisplayMoveError(_("It is White's turn"));
5836             } else if (toX >= 0 && toY >= 0) {
5837                 premoveToX = toX;
5838                 premoveToY = toY;
5839                 premoveFromX = fromX;
5840                 premoveFromY = fromY;
5841                 premovePromoChar = promoChar;
5842                 gotPremove = 1;
5843                 if (appData.debugMode) 
5844                     fprintf(debugFP, "Got premove: fromX %d,"
5845                             "fromY %d, toX %d, toY %d\n",
5846                             fromX, fromY, toX, toY);
5847             }
5848             return ImpossibleMove;
5849         }
5850         break;
5851
5852       case IcsPlayingWhite:
5853         /* User is moving for White */
5854         if (!WhiteOnMove(currentMove)) {
5855             if (!appData.premove) {
5856                 DisplayMoveError(_("It is Black's turn"));
5857             } else if (toX >= 0 && toY >= 0) {
5858                 premoveToX = toX;
5859                 premoveToY = toY;
5860                 premoveFromX = fromX;
5861                 premoveFromY = fromY;
5862                 premovePromoChar = promoChar;
5863                 gotPremove = 1;
5864                 if (appData.debugMode) 
5865                     fprintf(debugFP, "Got premove: fromX %d,"
5866                             "fromY %d, toX %d, toY %d\n",
5867                             fromX, fromY, toX, toY);
5868             }
5869             return ImpossibleMove;
5870         }
5871         break;
5872
5873       default:
5874         break;
5875
5876       case EditPosition:
5877         /* EditPosition, empty square, or different color piece;
5878            click-click move is possible */
5879         if (toX == -2 || toY == -2) {
5880             boards[0][fromY][fromX] = EmptySquare;
5881             return AmbiguousMove;
5882         } else if (toX >= 0 && toY >= 0) {
5883             boards[0][toY][toX] = boards[0][fromY][fromX];
5884             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5885                 if(boards[0][fromY][0] != EmptySquare) {
5886                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5887                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5888                 }
5889             } else
5890             if(fromX == BOARD_RGHT+1) {
5891                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5892                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5893                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5894                 }
5895             } else
5896             boards[0][fromY][fromX] = EmptySquare;
5897             return AmbiguousMove;
5898         }
5899         return ImpossibleMove;
5900     }
5901
5902     if(toX < 0 || toY < 0) return ImpossibleMove;
5903     pdown = boards[currentMove][fromY][fromX];
5904     pup = boards[currentMove][toY][toX];
5905
5906     /* [HGM] If move started in holdings, it means a drop */
5907     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5908          if( pup != EmptySquare ) return ImpossibleMove;
5909          if(appData.testLegality) {
5910              /* it would be more logical if LegalityTest() also figured out
5911               * which drops are legal. For now we forbid pawns on back rank.
5912               * Shogi is on its own here...
5913               */
5914              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5915                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5916                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5917          }
5918          return WhiteDrop; /* Not needed to specify white or black yet */
5919     }
5920
5921     /* [HGM] always test for legality, to get promotion info */
5922     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5923                                          fromY, fromX, toY, toX, promoChar);
5924     /* [HGM] but possibly ignore an IllegalMove result */
5925     if (appData.testLegality) {
5926         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5927             DisplayMoveError(_("Illegal move"));
5928             return ImpossibleMove;
5929         }
5930     }
5931
5932     return moveType;
5933     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5934        function is made into one that returns an OK move type if FinishMove
5935        should be called. This to give the calling driver routine the
5936        opportunity to finish the userMove input with a promotion popup,
5937        without bothering the user with this for invalid or illegal moves */
5938
5939 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5940 }
5941
5942 /* Common tail of UserMoveEvent and DropMenuEvent */
5943 int
5944 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5945      ChessMove moveType;
5946      int fromX, fromY, toX, toY;
5947      /*char*/int promoChar;
5948 {
5949     char *bookHit = 0;
5950
5951     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5952         // [HGM] superchess: suppress promotions to non-available piece
5953         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5954         if(WhiteOnMove(currentMove)) {
5955             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5956         } else {
5957             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5958         }
5959     }
5960
5961     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5962        move type in caller when we know the move is a legal promotion */
5963     if(moveType == NormalMove && promoChar)
5964         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5965
5966     /* [HGM] convert drag-and-drop piece drops to standard form */
5967     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5968          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5969            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5970                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5971            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5972            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5973            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5974            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5975          fromY = DROP_RANK;
5976     }
5977
5978     /* [HGM] <popupFix> The following if has been moved here from
5979        UserMoveEvent(). Because it seemed to belong here (why not allow
5980        piece drops in training games?), and because it can only be
5981        performed after it is known to what we promote. */
5982     if (gameMode == Training) {
5983       /* compare the move played on the board to the next move in the
5984        * game. If they match, display the move and the opponent's response. 
5985        * If they don't match, display an error message.
5986        */
5987       int saveAnimate;
5988       Board testBoard;
5989       CopyBoard(testBoard, boards[currentMove]);
5990       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5991
5992       if (CompareBoards(testBoard, boards[currentMove+1])) {
5993         ForwardInner(currentMove+1);
5994
5995         /* Autoplay the opponent's response.
5996          * if appData.animate was TRUE when Training mode was entered,
5997          * the response will be animated.
5998          */
5999         saveAnimate = appData.animate;
6000         appData.animate = animateTraining;
6001         ForwardInner(currentMove+1);
6002         appData.animate = saveAnimate;
6003
6004         /* check for the end of the game */
6005         if (currentMove >= forwardMostMove) {
6006           gameMode = PlayFromGameFile;
6007           ModeHighlight();
6008           SetTrainingModeOff();
6009           DisplayInformation(_("End of game"));
6010         }
6011       } else {
6012         DisplayError(_("Incorrect move"), 0);
6013       }
6014       return 1;
6015     }
6016
6017   /* Ok, now we know that the move is good, so we can kill
6018      the previous line in Analysis Mode */
6019   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6020                                 && currentMove < forwardMostMove) {
6021     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6022   }
6023
6024   /* If we need the chess program but it's dead, restart it */
6025   ResurrectChessProgram();
6026
6027   /* A user move restarts a paused game*/
6028   if (pausing)
6029     PauseEvent();
6030
6031   thinkOutput[0] = NULLCHAR;
6032
6033   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6034
6035   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6036
6037   if (gameMode == BeginningOfGame) {
6038     if (appData.noChessProgram) {
6039       gameMode = EditGame;
6040       SetGameInfo();
6041     } else {
6042       char buf[MSG_SIZ];
6043       gameMode = MachinePlaysBlack;
6044       StartClocks();
6045       SetGameInfo();
6046       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6047       DisplayTitle(buf);
6048       if (first.sendName) {
6049         sprintf(buf, "name %s\n", gameInfo.white);
6050         SendToProgram(buf, &first);
6051       }
6052       StartClocks();
6053     }
6054     ModeHighlight();
6055   }
6056
6057   /* Relay move to ICS or chess engine */
6058   if (appData.icsActive) {
6059     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6060         gameMode == IcsExamining) {
6061       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6062         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6063         SendToICS("draw ");
6064         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6065       }
6066       // also send plain move, in case ICS does not understand atomic claims
6067       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6068       ics_user_moved = 1;
6069     }
6070   } else {
6071     if (first.sendTime && (gameMode == BeginningOfGame ||
6072                            gameMode == MachinePlaysWhite ||
6073                            gameMode == MachinePlaysBlack)) {
6074       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6075     }
6076     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6077          // [HGM] book: if program might be playing, let it use book
6078         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6079         first.maybeThinking = TRUE;
6080     } else SendMoveToProgram(forwardMostMove-1, &first);
6081     if (currentMove == cmailOldMove + 1) {
6082       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6083     }
6084   }
6085
6086   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6087
6088   switch (gameMode) {
6089   case EditGame:
6090     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6091     case MT_NONE:
6092     case MT_CHECK:
6093       break;
6094     case MT_CHECKMATE:
6095     case MT_STAINMATE:
6096       if (WhiteOnMove(currentMove)) {
6097         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6098       } else {
6099         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6100       }
6101       break;
6102     case MT_STALEMATE:
6103       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6104       break;
6105     }
6106     break;
6107     
6108   case MachinePlaysBlack:
6109   case MachinePlaysWhite:
6110     /* disable certain menu options while machine is thinking */
6111     SetMachineThinkingEnables();
6112     break;
6113
6114   default:
6115     break;
6116   }
6117
6118   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6119         
6120   if(bookHit) { // [HGM] book: simulate book reply
6121         static char bookMove[MSG_SIZ]; // a bit generous?
6122
6123         programStats.nodes = programStats.depth = programStats.time = 
6124         programStats.score = programStats.got_only_move = 0;
6125         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6126
6127         strcpy(bookMove, "move ");
6128         strcat(bookMove, bookHit);
6129         HandleMachineMove(bookMove, &first);
6130   }
6131   return 1;
6132 }
6133
6134 void
6135 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6136      int fromX, fromY, toX, toY;
6137      int promoChar;
6138 {
6139     /* [HGM] This routine was added to allow calling of its two logical
6140        parts from other modules in the old way. Before, UserMoveEvent()
6141        automatically called FinishMove() if the move was OK, and returned
6142        otherwise. I separated the two, in order to make it possible to
6143        slip a promotion popup in between. But that it always needs two
6144        calls, to the first part, (now called UserMoveTest() ), and to
6145        FinishMove if the first part succeeded. Calls that do not need
6146        to do anything in between, can call this routine the old way. 
6147     */
6148     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6149 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6150     if(moveType == AmbiguousMove)
6151         DrawPosition(FALSE, boards[currentMove]);
6152     else if(moveType != ImpossibleMove && moveType != Comment)
6153         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6154 }
6155
6156 void
6157 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6158      Board board;
6159      int flags;
6160      ChessMove kind;
6161      int rf, ff, rt, ft;
6162      VOIDSTAR closure;
6163 {
6164     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6165     Markers *m = (Markers *) closure;
6166     if(rf == fromY && ff == fromX)
6167         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6168                          || kind == WhiteCapturesEnPassant
6169                          || kind == BlackCapturesEnPassant);
6170     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6171 }
6172
6173 void
6174 MarkTargetSquares(int clear)
6175 {
6176   int x, y;
6177   if(!appData.markers || !appData.highlightDragging || 
6178      !appData.testLegality || gameMode == EditPosition) return;
6179   if(clear) {
6180     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6181   } else {
6182     int capt = 0;
6183     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6184     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6185       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6186       if(capt)
6187       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6188     }
6189   }
6190   DrawPosition(TRUE, NULL);
6191 }
6192
6193 void LeftClick(ClickType clickType, int xPix, int yPix)
6194 {
6195     int x, y;
6196     Boolean saveAnimate;
6197     static int second = 0, promotionChoice = 0;
6198     char promoChoice = NULLCHAR;
6199
6200     if(appData.seekGraph && appData.icsActive && loggedOn &&
6201         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6202         SeekGraphClick(clickType, xPix, yPix, 0);
6203         return;
6204     }
6205
6206     if (clickType == Press) ErrorPopDown();
6207     MarkTargetSquares(1);
6208
6209     x = EventToSquare(xPix, BOARD_WIDTH);
6210     y = EventToSquare(yPix, BOARD_HEIGHT);
6211     if (!flipView && y >= 0) {
6212         y = BOARD_HEIGHT - 1 - y;
6213     }
6214     if (flipView && x >= 0) {
6215         x = BOARD_WIDTH - 1 - x;
6216     }
6217
6218     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6219         if(clickType == Release) return; // ignore upclick of click-click destination
6220         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6221         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6222         if(gameInfo.holdingsWidth && 
6223                 (WhiteOnMove(currentMove) 
6224                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6225                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6226             // click in right holdings, for determining promotion piece
6227             ChessSquare p = boards[currentMove][y][x];
6228             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6229             if(p != EmptySquare) {
6230                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6231                 fromX = fromY = -1;
6232                 return;
6233             }
6234         }
6235         DrawPosition(FALSE, boards[currentMove]);
6236         return;
6237     }
6238
6239     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6240     if(clickType == Press
6241             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6242               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6243               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6244         return;
6245
6246     autoQueen = appData.alwaysPromoteToQueen;
6247
6248     if (fromX == -1) {
6249       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6250         if (clickType == Press) {
6251             /* First square */
6252             if (OKToStartUserMove(x, y)) {
6253                 fromX = x;
6254                 fromY = y;
6255                 second = 0;
6256                 MarkTargetSquares(0);
6257                 DragPieceBegin(xPix, yPix);
6258                 if (appData.highlightDragging) {
6259                     SetHighlights(x, y, -1, -1);
6260                 }
6261             }
6262         }
6263         return;
6264       }
6265     }
6266
6267     /* fromX != -1 */
6268     if (clickType == Press && gameMode != EditPosition) {
6269         ChessSquare fromP;
6270         ChessSquare toP;
6271         int frc;
6272
6273         // ignore off-board to clicks
6274         if(y < 0 || x < 0) return;
6275
6276         /* Check if clicking again on the same color piece */
6277         fromP = boards[currentMove][fromY][fromX];
6278         toP = boards[currentMove][y][x];
6279         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6280         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6281              WhitePawn <= toP && toP <= WhiteKing &&
6282              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6283              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6284             (BlackPawn <= fromP && fromP <= BlackKing && 
6285              BlackPawn <= toP && toP <= BlackKing &&
6286              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6287              !(fromP == BlackKing && toP == BlackRook && frc))) {
6288             /* Clicked again on same color piece -- changed his mind */
6289             second = (x == fromX && y == fromY);
6290            if(!second || !OnlyMove(&x, &y, TRUE)) {
6291             if (appData.highlightDragging) {
6292                 SetHighlights(x, y, -1, -1);
6293             } else {
6294                 ClearHighlights();
6295             }
6296             if (OKToStartUserMove(x, y)) {
6297                 fromX = x;
6298                 fromY = y;
6299                 MarkTargetSquares(0);
6300                 DragPieceBegin(xPix, yPix);
6301             }
6302             return;
6303            }
6304         }
6305         // ignore clicks on holdings
6306         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6307     }
6308
6309     if (clickType == Release && x == fromX && y == fromY) {
6310         DragPieceEnd(xPix, yPix);
6311         if (appData.animateDragging) {
6312             /* Undo animation damage if any */
6313             DrawPosition(FALSE, NULL);
6314         }
6315         if (second) {
6316             /* Second up/down in same square; just abort move */
6317             second = 0;
6318             fromX = fromY = -1;
6319             ClearHighlights();
6320             gotPremove = 0;
6321             ClearPremoveHighlights();
6322         } else {
6323             /* First upclick in same square; start click-click mode */
6324             SetHighlights(x, y, -1, -1);
6325         }
6326         return;
6327     }
6328
6329     /* we now have a different from- and (possibly off-board) to-square */
6330     /* Completed move */
6331     toX = x;
6332     toY = y;
6333     saveAnimate = appData.animate;
6334     if (clickType == Press) {
6335         /* Finish clickclick move */
6336         if (appData.animate || appData.highlightLastMove) {
6337             SetHighlights(fromX, fromY, toX, toY);
6338         } else {
6339             ClearHighlights();
6340         }
6341     } else {
6342         /* Finish drag move */
6343         if (appData.highlightLastMove) {
6344             SetHighlights(fromX, fromY, toX, toY);
6345         } else {
6346             ClearHighlights();
6347         }
6348         DragPieceEnd(xPix, yPix);
6349         /* Don't animate move and drag both */
6350         appData.animate = FALSE;
6351     }
6352
6353     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6354     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6355         ChessSquare piece = boards[currentMove][fromY][fromX];
6356         if(gameMode == EditPosition && piece != EmptySquare &&
6357            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6358             int n;
6359              
6360             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6361                 n = PieceToNumber(piece - (int)BlackPawn);
6362                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6363                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6364                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6365             } else
6366             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6367                 n = PieceToNumber(piece);
6368                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6369                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6370                 boards[currentMove][n][BOARD_WIDTH-2]++;
6371             }
6372             boards[currentMove][fromY][fromX] = EmptySquare;
6373         }
6374         ClearHighlights();
6375         fromX = fromY = -1;
6376         DrawPosition(TRUE, boards[currentMove]);
6377         return;
6378     }
6379
6380     // off-board moves should not be highlighted
6381     if(x < 0 || x < 0) ClearHighlights();
6382
6383     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6384         SetHighlights(fromX, fromY, toX, toY);
6385         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6386             // [HGM] super: promotion to captured piece selected from holdings
6387             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6388             promotionChoice = TRUE;
6389             // kludge follows to temporarily execute move on display, without promoting yet
6390             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6391             boards[currentMove][toY][toX] = p;
6392             DrawPosition(FALSE, boards[currentMove]);
6393             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6394             boards[currentMove][toY][toX] = q;
6395             DisplayMessage("Click in holdings to choose piece", "");
6396             return;
6397         }
6398         PromotionPopUp();
6399     } else {
6400         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6401         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6402         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6403         fromX = fromY = -1;
6404     }
6405     appData.animate = saveAnimate;
6406     if (appData.animate || appData.animateDragging) {
6407         /* Undo animation damage if needed */
6408         DrawPosition(FALSE, NULL);
6409     }
6410 }
6411
6412 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6413 {   // front-end-free part taken out of PieceMenuPopup
6414     int whichMenu; int xSqr, ySqr;
6415
6416     if(seekGraphUp) { // [HGM] seekgraph
6417         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6418         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6419         return -2;
6420     }
6421
6422     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6423          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6424         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6425         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6426         return -2;
6427     }
6428
6429     xSqr = EventToSquare(x, BOARD_WIDTH);
6430     ySqr = EventToSquare(y, BOARD_HEIGHT);
6431     if (action == Release) UnLoadPV(); // [HGM] pv
6432     if (action != Press) return -2; // return code to be ignored
6433     switch (gameMode) {
6434       case IcsExamining:
6435         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6436       case EditPosition:
6437         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6438         if (xSqr < 0 || ySqr < 0) return -1;\r
6439         whichMenu = 0; // edit-position menu
6440         break;
6441       case IcsObserving:
6442         if(!appData.icsEngineAnalyze) return -1;
6443       case IcsPlayingWhite:
6444       case IcsPlayingBlack:
6445         if(!appData.zippyPlay) goto noZip;
6446       case AnalyzeMode:
6447       case AnalyzeFile:
6448       case MachinePlaysWhite:
6449       case MachinePlaysBlack:
6450       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6451         if (!appData.dropMenu) {
6452           LoadPV(x, y);
6453           return 2; // flag front-end to grab mouse events
6454         }
6455         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6456            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6457       case EditGame:
6458       noZip:
6459         if (xSqr < 0 || ySqr < 0) return -1;
6460         if (!appData.dropMenu || appData.testLegality &&
6461             gameInfo.variant != VariantBughouse &&
6462             gameInfo.variant != VariantCrazyhouse) return -1;
6463         whichMenu = 1; // drop menu
6464         break;
6465       default:
6466         return -1;
6467     }
6468
6469     if (((*fromX = xSqr) < 0) ||
6470         ((*fromY = ySqr) < 0)) {
6471         *fromX = *fromY = -1;
6472         return -1;
6473     }
6474     if (flipView)
6475       *fromX = BOARD_WIDTH - 1 - *fromX;
6476     else
6477       *fromY = BOARD_HEIGHT - 1 - *fromY;
6478
6479     return whichMenu;
6480 }
6481
6482 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6483 {
6484 //    char * hint = lastHint;
6485     FrontEndProgramStats stats;
6486
6487     stats.which = cps == &first ? 0 : 1;
6488     stats.depth = cpstats->depth;
6489     stats.nodes = cpstats->nodes;
6490     stats.score = cpstats->score;
6491     stats.time = cpstats->time;
6492     stats.pv = cpstats->movelist;
6493     stats.hint = lastHint;
6494     stats.an_move_index = 0;
6495     stats.an_move_count = 0;
6496
6497     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6498         stats.hint = cpstats->move_name;
6499         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6500         stats.an_move_count = cpstats->nr_moves;
6501     }
6502
6503     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6504
6505     SetProgramStats( &stats );
6506 }
6507
6508 int
6509 Adjudicate(ChessProgramState *cps)
6510 {       // [HGM] some adjudications useful with buggy engines
6511         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6512         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6513         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6514         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6515         int k, count = 0; static int bare = 1;
6516         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6517         Boolean canAdjudicate = !appData.icsActive;
6518
6519         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6520         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6521             if( appData.testLegality )
6522             {   /* [HGM] Some more adjudications for obstinate engines */
6523                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6524                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6525                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6526                 static int moveCount = 6;
6527                 ChessMove result;
6528                 char *reason = NULL;
6529
6530                 /* Count what is on board. */
6531                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6532                 {   ChessSquare p = boards[forwardMostMove][i][j];
6533                     int m=i;
6534
6535                     switch((int) p)
6536                     {   /* count B,N,R and other of each side */
6537                         case WhiteKing:
6538                         case BlackKing:
6539                              NrK++; break; // [HGM] atomic: count Kings
6540                         case WhiteKnight:
6541                              NrWN++; break;
6542                         case WhiteBishop:
6543                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6544                              bishopsColor |= 1 << ((i^j)&1);
6545                              NrWB++; break;
6546                         case BlackKnight:
6547                              NrBN++; break;
6548                         case BlackBishop:
6549                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6550                              bishopsColor |= 1 << ((i^j)&1);
6551                              NrBB++; break;
6552                         case WhiteRook:
6553                              NrWR++; break;
6554                         case BlackRook:
6555                              NrBR++; break;
6556                         case WhiteQueen:
6557                              NrWQ++; break;
6558                         case BlackQueen:
6559                              NrBQ++; break;
6560                         case EmptySquare: 
6561                              break;
6562                         case BlackPawn:
6563                              m = 7-i;
6564                         case WhitePawn:
6565                              PawnAdvance += m; NrPawns++;
6566                     }
6567                     NrPieces += (p != EmptySquare);
6568                     NrW += ((int)p < (int)BlackPawn);
6569                     if(gameInfo.variant == VariantXiangqi && 
6570                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6571                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6572                         NrW -= ((int)p < (int)BlackPawn);
6573                     }
6574                 }
6575
6576                 /* Some material-based adjudications that have to be made before stalemate test */
6577                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6578                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6579                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6580                      if(canAdjudicate && appData.checkMates) {
6581                          if(engineOpponent)
6582                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6583                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6584                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6585                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6586                          return 1;
6587                      }
6588                 }
6589
6590                 /* Bare King in Shatranj (loses) or Losers (wins) */
6591                 if( NrW == 1 || NrPieces - NrW == 1) {
6592                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6593                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6594                      if(canAdjudicate && appData.checkMates) {
6595                          if(engineOpponent)
6596                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6597                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6598                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6599                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6600                          return 1;
6601                      }
6602                   } else
6603                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6604                   {    /* bare King */
6605                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6606                         if(canAdjudicate && appData.checkMates) {
6607                             /* but only adjudicate if adjudication enabled */
6608                             if(engineOpponent)
6609                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6610                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6611                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6612                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6613                             return 1;
6614                         }
6615                   }
6616                 } else bare = 1;
6617
6618
6619             // don't wait for engine to announce game end if we can judge ourselves
6620             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6621               case MT_CHECK:
6622                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6623                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6624                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6625                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6626                             checkCnt++;
6627                         if(checkCnt >= 2) {
6628                             reason = "Xboard adjudication: 3rd check";
6629                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6630                             break;
6631                         }
6632                     }
6633                 }
6634               case MT_NONE:
6635               default:
6636                 break;
6637               case MT_STALEMATE:
6638               case MT_STAINMATE:
6639                 reason = "Xboard adjudication: Stalemate";
6640                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6641                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6642                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6643                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6644                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6645                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6646                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6647                                                                         EP_CHECKMATE : EP_WINS);
6648                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6649                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6650                 }
6651                 break;
6652               case MT_CHECKMATE:
6653                 reason = "Xboard adjudication: Checkmate";
6654                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6655                 break;
6656             }
6657
6658                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6659                     case EP_STALEMATE:
6660                         result = GameIsDrawn; break;
6661                     case EP_CHECKMATE:
6662                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6663                     case EP_WINS:
6664                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6665                     default:
6666                         result = (ChessMove) 0;
6667                 }
6668                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6669                     if(engineOpponent)
6670                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6671                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6672                     GameEnds( result, reason, GE_XBOARD );
6673                     return 1;
6674                 }
6675
6676                 /* Next absolutely insufficient mating material. */
6677                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6678                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6679                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6680                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6681                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6682
6683                      /* always flag draws, for judging claims */
6684                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6685
6686                      if(canAdjudicate && appData.materialDraws) {
6687                          /* but only adjudicate them if adjudication enabled */
6688                          if(engineOpponent) {
6689                            SendToProgram("force\n", engineOpponent); // suppress reply
6690                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6691                          }
6692                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6693                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6694                          return 1;
6695                      }
6696                 }
6697
6698                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6699                 if(NrPieces == 4 && 
6700                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6701                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6702                    || NrWN==2 || NrBN==2     /* KNNK */
6703                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6704                   ) ) {
6705                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6706                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6707                           if(engineOpponent) {
6708                             SendToProgram("force\n", engineOpponent); // suppress reply
6709                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6710                           }
6711                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6712                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6713                           return 1;
6714                      }
6715                 } else moveCount = 6;
6716             }
6717         }
6718           
6719         if (appData.debugMode) { int i;
6720             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6721                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6722                     appData.drawRepeats);
6723             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6724               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6725             
6726         }
6727
6728         // Repetition draws and 50-move rule can be applied independently of legality testing
6729
6730                 /* Check for rep-draws */
6731                 count = 0;
6732                 for(k = forwardMostMove-2;
6733                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6734                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6735                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6736                     k-=2)
6737                 {   int rights=0;
6738                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6739                         /* compare castling rights */
6740                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6741                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6742                                 rights++; /* King lost rights, while rook still had them */
6743                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6744                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6745                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6746                                    rights++; /* but at least one rook lost them */
6747                         }
6748                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6749                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6750                                 rights++; 
6751                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6752                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6753                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6754                                    rights++;
6755                         }
6756                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6757                             && appData.drawRepeats > 1) {
6758                              /* adjudicate after user-specified nr of repeats */
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                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6765                                 // [HGM] xiangqi: check for forbidden perpetuals
6766                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6767                                 for(m=forwardMostMove; m>k; m-=2) {
6768                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6769                                         ourPerpetual = 0; // the current mover did not always check
6770                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6771                                         hisPerpetual = 0; // the opponent did not always check
6772                                 }
6773                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6774                                                                         ourPerpetual, hisPerpetual);
6775                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6776                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6777                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6778                                     return 1;
6779                                 }
6780                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6781                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6782                                 // Now check for perpetual chases
6783                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6784                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6785                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6786                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6787                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6788                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6789                                         return 1;
6790                                     }
6791                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6792                                         break; // Abort repetition-checking loop.
6793                                 }
6794                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6795                              }
6796                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6797                              return 1;
6798                         }
6799                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6800                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6801                     }
6802                 }
6803
6804                 /* Now we test for 50-move draws. Determine ply count */
6805                 count = forwardMostMove;
6806                 /* look for last irreversble move */
6807                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6808                     count--;
6809                 /* if we hit starting position, add initial plies */
6810                 if( count == backwardMostMove )
6811                     count -= initialRulePlies;
6812                 count = forwardMostMove - count; 
6813                 if( count >= 100)
6814                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6815                          /* this is used to judge if draw claims are legal */
6816                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6817                          if(engineOpponent) {
6818                            SendToProgram("force\n", engineOpponent); // suppress reply
6819                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6820                          }
6821                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6822                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6823                          return 1;
6824                 }
6825
6826                 /* if draw offer is pending, treat it as a draw claim
6827                  * when draw condition present, to allow engines a way to
6828                  * claim draws before making their move to avoid a race
6829                  * condition occurring after their move
6830                  */
6831                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6832                          char *p = NULL;
6833                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6834                              p = "Draw claim: 50-move rule";
6835                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6836                              p = "Draw claim: 3-fold repetition";
6837                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6838                              p = "Draw claim: insufficient mating material";
6839                          if( p != NULL && canAdjudicate) {
6840                              if(engineOpponent) {
6841                                SendToProgram("force\n", engineOpponent); // suppress reply
6842                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6843                              }
6844                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6845                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6846                              return 1;
6847                          }
6848                 }
6849
6850                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6851                     if(engineOpponent) {
6852                       SendToProgram("force\n", engineOpponent); // suppress reply
6853                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6854                     }
6855                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6856                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6857                     return 1;
6858                 }
6859         return 0;
6860 }
6861
6862 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6863 {   // [HGM] book: this routine intercepts moves to simulate book replies
6864     char *bookHit = NULL;
6865
6866     //first determine if the incoming move brings opponent into his book
6867     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6868         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6869     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6870     if(bookHit != NULL && !cps->bookSuspend) {
6871         // make sure opponent is not going to reply after receiving move to book position
6872         SendToProgram("force\n", cps);
6873         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6874     }
6875     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6876     // now arrange restart after book miss
6877     if(bookHit) {
6878         // after a book hit we never send 'go', and the code after the call to this routine
6879         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6880         char buf[MSG_SIZ];
6881         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6882         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6883         SendToProgram(buf, cps);
6884         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6885     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6886         SendToProgram("go\n", cps);
6887         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6888     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6889         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6890             SendToProgram("go\n", cps); 
6891         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6892     }
6893     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6894 }
6895
6896 char *savedMessage;
6897 ChessProgramState *savedState;
6898 void DeferredBookMove(void)
6899 {
6900         if(savedState->lastPing != savedState->lastPong)
6901                     ScheduleDelayedEvent(DeferredBookMove, 10);
6902         else
6903         HandleMachineMove(savedMessage, savedState);
6904 }
6905
6906 void
6907 HandleMachineMove(message, cps)
6908      char *message;
6909      ChessProgramState *cps;
6910 {
6911     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6912     char realname[MSG_SIZ];
6913     int fromX, fromY, toX, toY;
6914     ChessMove moveType;
6915     char promoChar;
6916     char *p;
6917     int machineWhite;
6918     char *bookHit;
6919
6920     cps->userError = 0;
6921
6922 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6923     /*
6924      * Kludge to ignore BEL characters
6925      */
6926     while (*message == '\007') message++;
6927
6928     /*
6929      * [HGM] engine debug message: ignore lines starting with '#' character
6930      */
6931     if(cps->debug && *message == '#') return;
6932
6933     /*
6934      * Look for book output
6935      */
6936     if (cps == &first && bookRequested) {
6937         if (message[0] == '\t' || message[0] == ' ') {
6938             /* Part of the book output is here; append it */
6939             strcat(bookOutput, message);
6940             strcat(bookOutput, "  \n");
6941             return;
6942         } else if (bookOutput[0] != NULLCHAR) {
6943             /* All of book output has arrived; display it */
6944             char *p = bookOutput;
6945             while (*p != NULLCHAR) {
6946                 if (*p == '\t') *p = ' ';
6947                 p++;
6948             }
6949             DisplayInformation(bookOutput);
6950             bookRequested = FALSE;
6951             /* Fall through to parse the current output */
6952         }
6953     }
6954
6955     /*
6956      * Look for machine move.
6957      */
6958     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6959         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6960     {
6961         /* This method is only useful on engines that support ping */
6962         if (cps->lastPing != cps->lastPong) {
6963           if (gameMode == BeginningOfGame) {
6964             /* Extra move from before last new; ignore */
6965             if (appData.debugMode) {
6966                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6967             }
6968           } else {
6969             if (appData.debugMode) {
6970                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6971                         cps->which, gameMode);
6972             }
6973
6974             SendToProgram("undo\n", cps);
6975           }
6976           return;
6977         }
6978
6979         switch (gameMode) {
6980           case BeginningOfGame:
6981             /* Extra move from before last reset; ignore */
6982             if (appData.debugMode) {
6983                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6984             }
6985             return;
6986
6987           case EndOfGame:
6988           case IcsIdle:
6989           default:
6990             /* Extra move after we tried to stop.  The mode test is
6991                not a reliable way of detecting this problem, but it's
6992                the best we can do on engines that don't support ping.
6993             */
6994             if (appData.debugMode) {
6995                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6996                         cps->which, gameMode);
6997             }
6998             SendToProgram("undo\n", cps);
6999             return;
7000
7001           case MachinePlaysWhite:
7002           case IcsPlayingWhite:
7003             machineWhite = TRUE;
7004             break;
7005
7006           case MachinePlaysBlack:
7007           case IcsPlayingBlack:
7008             machineWhite = FALSE;
7009             break;
7010
7011           case TwoMachinesPlay:
7012             machineWhite = (cps->twoMachinesColor[0] == 'w');
7013             break;
7014         }
7015         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7016             if (appData.debugMode) {
7017                 fprintf(debugFP,
7018                         "Ignoring move out of turn by %s, gameMode %d"
7019                         ", forwardMost %d\n",
7020                         cps->which, gameMode, forwardMostMove);
7021             }
7022             return;
7023         }
7024
7025     if (appData.debugMode) { int f = forwardMostMove;
7026         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7027                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7028                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7029     }
7030         if(cps->alphaRank) AlphaRank(machineMove, 4);
7031         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7032                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7033             /* Machine move could not be parsed; ignore it. */
7034             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7035                     machineMove, cps->which);
7036             DisplayError(buf1, 0);
7037             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7038                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7039             if (gameMode == TwoMachinesPlay) {
7040               GameEnds(machineWhite ? BlackWins : WhiteWins,
7041                        buf1, GE_XBOARD);
7042             }
7043             return;
7044         }
7045
7046         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7047         /* So we have to redo legality test with true e.p. status here,  */
7048         /* to make sure an illegal e.p. capture does not slip through,   */
7049         /* to cause a forfeit on a justified illegal-move complaint      */
7050         /* of the opponent.                                              */
7051         if( gameMode==TwoMachinesPlay && appData.testLegality
7052             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7053                                                               ) {
7054            ChessMove moveType;
7055            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7056                              fromY, fromX, toY, toX, promoChar);
7057             if (appData.debugMode) {
7058                 int i;
7059                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7060                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7061                 fprintf(debugFP, "castling rights\n");
7062             }
7063             if(moveType == IllegalMove) {
7064                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7065                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7066                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7067                            buf1, GE_XBOARD);
7068                 return;
7069            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7070            /* [HGM] Kludge to handle engines that send FRC-style castling
7071               when they shouldn't (like TSCP-Gothic) */
7072            switch(moveType) {
7073              case WhiteASideCastleFR:
7074              case BlackASideCastleFR:
7075                toX+=2;
7076                currentMoveString[2]++;
7077                break;
7078              case WhiteHSideCastleFR:
7079              case BlackHSideCastleFR:
7080                toX--;
7081                currentMoveString[2]--;
7082                break;
7083              default: ; // nothing to do, but suppresses warning of pedantic compilers
7084            }
7085         }
7086         hintRequested = FALSE;
7087         lastHint[0] = NULLCHAR;
7088         bookRequested = FALSE;
7089         /* Program may be pondering now */
7090         cps->maybeThinking = TRUE;
7091         if (cps->sendTime == 2) cps->sendTime = 1;
7092         if (cps->offeredDraw) cps->offeredDraw--;
7093
7094         /* currentMoveString is set as a side-effect of ParseOneMove */
7095         strcpy(machineMove, currentMoveString);
7096         strcat(machineMove, "\n");
7097         strcpy(moveList[forwardMostMove], machineMove);
7098
7099         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7100
7101         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7102         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7103             int count = 0;
7104
7105             while( count < adjudicateLossPlies ) {
7106                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7107
7108                 if( count & 1 ) {
7109                     score = -score; /* Flip score for winning side */
7110                 }
7111
7112                 if( score > adjudicateLossThreshold ) {
7113                     break;
7114                 }
7115
7116                 count++;
7117             }
7118
7119             if( count >= adjudicateLossPlies ) {
7120                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7121
7122                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7123                     "Xboard adjudication", 
7124                     GE_XBOARD );
7125
7126                 return;
7127             }
7128         }
7129
7130         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7131
7132 #if ZIPPY
7133         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7134             first.initDone) {
7135           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7136                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7137                 SendToICS("draw ");
7138                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7139           }
7140           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7141           ics_user_moved = 1;
7142           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7143                 char buf[3*MSG_SIZ];
7144
7145                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7146                         programStats.score / 100.,
7147                         programStats.depth,
7148                         programStats.time / 100.,
7149                         (unsigned int)programStats.nodes,
7150                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7151                         programStats.movelist);
7152                 SendToICS(buf);
7153 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7154           }
7155         }
7156 #endif
7157
7158         /* [AS] Save move info and clear stats for next move */
7159         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7160         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7161         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7162         ClearProgramStats();
7163         thinkOutput[0] = NULLCHAR;
7164         hiddenThinkOutputState = 0;
7165
7166         bookHit = NULL;
7167         if (gameMode == TwoMachinesPlay) {
7168             /* [HGM] relaying draw offers moved to after reception of move */
7169             /* and interpreting offer as claim if it brings draw condition */
7170             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7171                 SendToProgram("draw\n", cps->other);
7172             }
7173             if (cps->other->sendTime) {
7174                 SendTimeRemaining(cps->other,
7175                                   cps->other->twoMachinesColor[0] == 'w');
7176             }
7177             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7178             if (firstMove && !bookHit) {
7179                 firstMove = FALSE;
7180                 if (cps->other->useColors) {
7181                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7182                 }
7183                 SendToProgram("go\n", cps->other);
7184             }
7185             cps->other->maybeThinking = TRUE;
7186         }
7187
7188         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7189         
7190         if (!pausing && appData.ringBellAfterMoves) {
7191             RingBell();
7192         }
7193
7194         /* 
7195          * Reenable menu items that were disabled while
7196          * machine was thinking
7197          */
7198         if (gameMode != TwoMachinesPlay)
7199             SetUserThinkingEnables();
7200
7201         // [HGM] book: after book hit opponent has received move and is now in force mode
7202         // force the book reply into it, and then fake that it outputted this move by jumping
7203         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7204         if(bookHit) {
7205                 static char bookMove[MSG_SIZ]; // a bit generous?
7206
7207                 strcpy(bookMove, "move ");
7208                 strcat(bookMove, bookHit);
7209                 message = bookMove;
7210                 cps = cps->other;
7211                 programStats.nodes = programStats.depth = programStats.time = 
7212                 programStats.score = programStats.got_only_move = 0;
7213                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7214
7215                 if(cps->lastPing != cps->lastPong) {
7216                     savedMessage = message; // args for deferred call
7217                     savedState = cps;
7218                     ScheduleDelayedEvent(DeferredBookMove, 10);
7219                     return;
7220                 }
7221                 goto FakeBookMove;
7222         }
7223
7224         return;
7225     }
7226
7227     /* Set special modes for chess engines.  Later something general
7228      *  could be added here; for now there is just one kludge feature,
7229      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7230      *  when "xboard" is given as an interactive command.
7231      */
7232     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7233         cps->useSigint = FALSE;
7234         cps->useSigterm = FALSE;
7235     }
7236     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7237       ParseFeatures(message+8, cps);
7238       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7239     }
7240
7241     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7242      * want this, I was asked to put it in, and obliged.
7243      */
7244     if (!strncmp(message, "setboard ", 9)) {
7245         Board initial_position;
7246
7247         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7248
7249         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7250             DisplayError(_("Bad FEN received from engine"), 0);
7251             return ;
7252         } else {
7253            Reset(TRUE, FALSE);
7254            CopyBoard(boards[0], initial_position);
7255            initialRulePlies = FENrulePlies;
7256            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7257            else gameMode = MachinePlaysBlack;                 
7258            DrawPosition(FALSE, boards[currentMove]);
7259         }
7260         return;
7261     }
7262
7263     /*
7264      * Look for communication commands
7265      */
7266     if (!strncmp(message, "telluser ", 9)) {
7267         DisplayNote(message + 9);
7268         return;
7269     }
7270     if (!strncmp(message, "tellusererror ", 14)) {
7271         cps->userError = 1;
7272         DisplayError(message + 14, 0);
7273         return;
7274     }
7275     if (!strncmp(message, "tellopponent ", 13)) {
7276       if (appData.icsActive) {
7277         if (loggedOn) {
7278           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7279           SendToICS(buf1);
7280         }
7281       } else {
7282         DisplayNote(message + 13);
7283       }
7284       return;
7285     }
7286     if (!strncmp(message, "tellothers ", 11)) {
7287       if (appData.icsActive) {
7288         if (loggedOn) {
7289           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7290           SendToICS(buf1);
7291         }
7292       }
7293       return;
7294     }
7295     if (!strncmp(message, "tellall ", 8)) {
7296       if (appData.icsActive) {
7297         if (loggedOn) {
7298           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7299           SendToICS(buf1);
7300         }
7301       } else {
7302         DisplayNote(message + 8);
7303       }
7304       return;
7305     }
7306     if (strncmp(message, "warning", 7) == 0) {
7307         /* Undocumented feature, use tellusererror in new code */
7308         DisplayError(message, 0);
7309         return;
7310     }
7311     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7312         strcpy(realname, cps->tidy);
7313         strcat(realname, " query");
7314         AskQuestion(realname, buf2, buf1, cps->pr);
7315         return;
7316     }
7317     /* Commands from the engine directly to ICS.  We don't allow these to be 
7318      *  sent until we are logged on. Crafty kibitzes have been known to 
7319      *  interfere with the login process.
7320      */
7321     if (loggedOn) {
7322         if (!strncmp(message, "tellics ", 8)) {
7323             SendToICS(message + 8);
7324             SendToICS("\n");
7325             return;
7326         }
7327         if (!strncmp(message, "tellicsnoalias ", 15)) {
7328             SendToICS(ics_prefix);
7329             SendToICS(message + 15);
7330             SendToICS("\n");
7331             return;
7332         }
7333         /* The following are for backward compatibility only */
7334         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7335             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7336             SendToICS(ics_prefix);
7337             SendToICS(message);
7338             SendToICS("\n");
7339             return;
7340         }
7341     }
7342     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7343         return;
7344     }
7345     /*
7346      * If the move is illegal, cancel it and redraw the board.
7347      * Also deal with other error cases.  Matching is rather loose
7348      * here to accommodate engines written before the spec.
7349      */
7350     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7351         strncmp(message, "Error", 5) == 0) {
7352         if (StrStr(message, "name") || 
7353             StrStr(message, "rating") || StrStr(message, "?") ||
7354             StrStr(message, "result") || StrStr(message, "board") ||
7355             StrStr(message, "bk") || StrStr(message, "computer") ||
7356             StrStr(message, "variant") || StrStr(message, "hint") ||
7357             StrStr(message, "random") || StrStr(message, "depth") ||
7358             StrStr(message, "accepted")) {
7359             return;
7360         }
7361         if (StrStr(message, "protover")) {
7362           /* Program is responding to input, so it's apparently done
7363              initializing, and this error message indicates it is
7364              protocol version 1.  So we don't need to wait any longer
7365              for it to initialize and send feature commands. */
7366           FeatureDone(cps, 1);
7367           cps->protocolVersion = 1;
7368           return;
7369         }
7370         cps->maybeThinking = FALSE;
7371
7372         if (StrStr(message, "draw")) {
7373             /* Program doesn't have "draw" command */
7374             cps->sendDrawOffers = 0;
7375             return;
7376         }
7377         if (cps->sendTime != 1 &&
7378             (StrStr(message, "time") || StrStr(message, "otim"))) {
7379           /* Program apparently doesn't have "time" or "otim" command */
7380           cps->sendTime = 0;
7381           return;
7382         }
7383         if (StrStr(message, "analyze")) {
7384             cps->analysisSupport = FALSE;
7385             cps->analyzing = FALSE;
7386             Reset(FALSE, TRUE);
7387             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7388             DisplayError(buf2, 0);
7389             return;
7390         }
7391         if (StrStr(message, "(no matching move)st")) {
7392           /* Special kludge for GNU Chess 4 only */
7393           cps->stKludge = TRUE;
7394           SendTimeControl(cps, movesPerSession, timeControl,
7395                           timeIncrement, appData.searchDepth,
7396                           searchTime);
7397           return;
7398         }
7399         if (StrStr(message, "(no matching move)sd")) {
7400           /* Special kludge for GNU Chess 4 only */
7401           cps->sdKludge = TRUE;
7402           SendTimeControl(cps, movesPerSession, timeControl,
7403                           timeIncrement, appData.searchDepth,
7404                           searchTime);
7405           return;
7406         }
7407         if (!StrStr(message, "llegal")) {
7408             return;
7409         }
7410         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7411             gameMode == IcsIdle) return;
7412         if (forwardMostMove <= backwardMostMove) return;
7413         if (pausing) PauseEvent();
7414       if(appData.forceIllegal) {
7415             // [HGM] illegal: machine refused move; force position after move into it
7416           SendToProgram("force\n", cps);
7417           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7418                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7419                 // when black is to move, while there might be nothing on a2 or black
7420                 // might already have the move. So send the board as if white has the move.
7421                 // But first we must change the stm of the engine, as it refused the last move
7422                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7423                 if(WhiteOnMove(forwardMostMove)) {
7424                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7425                     SendBoard(cps, forwardMostMove); // kludgeless board
7426                 } else {
7427                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7428                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7429                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7430                 }
7431           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7432             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7433                  gameMode == TwoMachinesPlay)
7434               SendToProgram("go\n", cps);
7435             return;
7436       } else
7437         if (gameMode == PlayFromGameFile) {
7438             /* Stop reading this game file */
7439             gameMode = EditGame;
7440             ModeHighlight();
7441         }
7442         currentMove = forwardMostMove-1;
7443         DisplayMove(currentMove-1); /* before DisplayMoveError */
7444         SwitchClocks(forwardMostMove-1); // [HGM] race
7445         DisplayBothClocks();
7446         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7447                 parseList[currentMove], cps->which);
7448         DisplayMoveError(buf1);
7449         DrawPosition(FALSE, boards[currentMove]);
7450
7451         /* [HGM] illegal-move claim should forfeit game when Xboard */
7452         /* only passes fully legal moves                            */
7453         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7454             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7455                                 "False illegal-move claim", GE_XBOARD );
7456         }
7457         return;
7458     }
7459     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7460         /* Program has a broken "time" command that
7461            outputs a string not ending in newline.
7462            Don't use it. */
7463         cps->sendTime = 0;
7464     }
7465     
7466     /*
7467      * If chess program startup fails, exit with an error message.
7468      * Attempts to recover here are futile.
7469      */
7470     if ((StrStr(message, "unknown host") != NULL)
7471         || (StrStr(message, "No remote directory") != NULL)
7472         || (StrStr(message, "not found") != NULL)
7473         || (StrStr(message, "No such file") != NULL)
7474         || (StrStr(message, "can't alloc") != NULL)
7475         || (StrStr(message, "Permission denied") != NULL)) {
7476
7477         cps->maybeThinking = FALSE;
7478         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7479                 cps->which, cps->program, cps->host, message);
7480         RemoveInputSource(cps->isr);
7481         DisplayFatalError(buf1, 0, 1);
7482         return;
7483     }
7484     
7485     /* 
7486      * Look for hint output
7487      */
7488     if (sscanf(message, "Hint: %s", buf1) == 1) {
7489         if (cps == &first && hintRequested) {
7490             hintRequested = FALSE;
7491             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7492                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7493                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7494                                     PosFlags(forwardMostMove),
7495                                     fromY, fromX, toY, toX, promoChar, buf1);
7496                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7497                 DisplayInformation(buf2);
7498             } else {
7499                 /* Hint move could not be parsed!? */
7500               snprintf(buf2, sizeof(buf2),
7501                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7502                         buf1, cps->which);
7503                 DisplayError(buf2, 0);
7504             }
7505         } else {
7506             strcpy(lastHint, buf1);
7507         }
7508         return;
7509     }
7510
7511     /*
7512      * Ignore other messages if game is not in progress
7513      */
7514     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7515         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7516
7517     /*
7518      * look for win, lose, draw, or draw offer
7519      */
7520     if (strncmp(message, "1-0", 3) == 0) {
7521         char *p, *q, *r = "";
7522         p = strchr(message, '{');
7523         if (p) {
7524             q = strchr(p, '}');
7525             if (q) {
7526                 *q = NULLCHAR;
7527                 r = p + 1;
7528             }
7529         }
7530         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7531         return;
7532     } else if (strncmp(message, "0-1", 3) == 0) {
7533         char *p, *q, *r = "";
7534         p = strchr(message, '{');
7535         if (p) {
7536             q = strchr(p, '}');
7537             if (q) {
7538                 *q = NULLCHAR;
7539                 r = p + 1;
7540             }
7541         }
7542         /* Kludge for Arasan 4.1 bug */
7543         if (strcmp(r, "Black resigns") == 0) {
7544             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7545             return;
7546         }
7547         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7548         return;
7549     } else if (strncmp(message, "1/2", 3) == 0) {
7550         char *p, *q, *r = "";
7551         p = strchr(message, '{');
7552         if (p) {
7553             q = strchr(p, '}');
7554             if (q) {
7555                 *q = NULLCHAR;
7556                 r = p + 1;
7557             }
7558         }
7559             
7560         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7561         return;
7562
7563     } else if (strncmp(message, "White resign", 12) == 0) {
7564         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7565         return;
7566     } else if (strncmp(message, "Black resign", 12) == 0) {
7567         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7568         return;
7569     } else if (strncmp(message, "White matches", 13) == 0 ||
7570                strncmp(message, "Black matches", 13) == 0   ) {
7571         /* [HGM] ignore GNUShogi noises */
7572         return;
7573     } else if (strncmp(message, "White", 5) == 0 &&
7574                message[5] != '(' &&
7575                StrStr(message, "Black") == NULL) {
7576         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7577         return;
7578     } else if (strncmp(message, "Black", 5) == 0 &&
7579                message[5] != '(') {
7580         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7581         return;
7582     } else if (strcmp(message, "resign") == 0 ||
7583                strcmp(message, "computer resigns") == 0) {
7584         switch (gameMode) {
7585           case MachinePlaysBlack:
7586           case IcsPlayingBlack:
7587             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7588             break;
7589           case MachinePlaysWhite:
7590           case IcsPlayingWhite:
7591             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7592             break;
7593           case TwoMachinesPlay:
7594             if (cps->twoMachinesColor[0] == 'w')
7595               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7596             else
7597               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7598             break;
7599           default:
7600             /* can't happen */
7601             break;
7602         }
7603         return;
7604     } else if (strncmp(message, "opponent mates", 14) == 0) {
7605         switch (gameMode) {
7606           case MachinePlaysBlack:
7607           case IcsPlayingBlack:
7608             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7609             break;
7610           case MachinePlaysWhite:
7611           case IcsPlayingWhite:
7612             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7613             break;
7614           case TwoMachinesPlay:
7615             if (cps->twoMachinesColor[0] == 'w')
7616               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7617             else
7618               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7619             break;
7620           default:
7621             /* can't happen */
7622             break;
7623         }
7624         return;
7625     } else if (strncmp(message, "computer mates", 14) == 0) {
7626         switch (gameMode) {
7627           case MachinePlaysBlack:
7628           case IcsPlayingBlack:
7629             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7630             break;
7631           case MachinePlaysWhite:
7632           case IcsPlayingWhite:
7633             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7634             break;
7635           case TwoMachinesPlay:
7636             if (cps->twoMachinesColor[0] == 'w')
7637               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7638             else
7639               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7640             break;
7641           default:
7642             /* can't happen */
7643             break;
7644         }
7645         return;
7646     } else if (strncmp(message, "checkmate", 9) == 0) {
7647         if (WhiteOnMove(forwardMostMove)) {
7648             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7649         } else {
7650             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7651         }
7652         return;
7653     } else if (strstr(message, "Draw") != NULL ||
7654                strstr(message, "game is a draw") != NULL) {
7655         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7656         return;
7657     } else if (strstr(message, "offer") != NULL &&
7658                strstr(message, "draw") != NULL) {
7659 #if ZIPPY
7660         if (appData.zippyPlay && first.initDone) {
7661             /* Relay offer to ICS */
7662             SendToICS(ics_prefix);
7663             SendToICS("draw\n");
7664         }
7665 #endif
7666         cps->offeredDraw = 2; /* valid until this engine moves twice */
7667         if (gameMode == TwoMachinesPlay) {
7668             if (cps->other->offeredDraw) {
7669                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7670             /* [HGM] in two-machine mode we delay relaying draw offer      */
7671             /* until after we also have move, to see if it is really claim */
7672             }
7673         } else if (gameMode == MachinePlaysWhite ||
7674                    gameMode == MachinePlaysBlack) {
7675           if (userOfferedDraw) {
7676             DisplayInformation(_("Machine accepts your draw offer"));
7677             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7678           } else {
7679             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7680           }
7681         }
7682     }
7683
7684     
7685     /*
7686      * Look for thinking output
7687      */
7688     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7689           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7690                                 ) {
7691         int plylev, mvleft, mvtot, curscore, time;
7692         char mvname[MOVE_LEN];
7693         u64 nodes; // [DM]
7694         char plyext;
7695         int ignore = FALSE;
7696         int prefixHint = FALSE;
7697         mvname[0] = NULLCHAR;
7698
7699         switch (gameMode) {
7700           case MachinePlaysBlack:
7701           case IcsPlayingBlack:
7702             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7703             break;
7704           case MachinePlaysWhite:
7705           case IcsPlayingWhite:
7706             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7707             break;
7708           case AnalyzeMode:
7709           case AnalyzeFile:
7710             break;
7711           case IcsObserving: /* [DM] icsEngineAnalyze */
7712             if (!appData.icsEngineAnalyze) ignore = TRUE;
7713             break;
7714           case TwoMachinesPlay:
7715             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7716                 ignore = TRUE;
7717             }
7718             break;
7719           default:
7720             ignore = TRUE;
7721             break;
7722         }
7723
7724         if (!ignore) {
7725             buf1[0] = NULLCHAR;
7726             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7727                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7728
7729                 if (plyext != ' ' && plyext != '\t') {
7730                     time *= 100;
7731                 }
7732
7733                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7734                 if( cps->scoreIsAbsolute && 
7735                     ( gameMode == MachinePlaysBlack ||
7736                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7737                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7738                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7739                      !WhiteOnMove(currentMove)
7740                     ) )
7741                 {
7742                     curscore = -curscore;
7743                 }
7744
7745
7746                 programStats.depth = plylev;
7747                 programStats.nodes = nodes;
7748                 programStats.time = time;
7749                 programStats.score = curscore;
7750                 programStats.got_only_move = 0;
7751
7752                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7753                         int ticklen;
7754
7755                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7756                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7757                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7758                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7759                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7760                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7761                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7762                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7763                 }
7764
7765                 /* Buffer overflow protection */
7766                 if (buf1[0] != NULLCHAR) {
7767                     if (strlen(buf1) >= sizeof(programStats.movelist)
7768                         && appData.debugMode) {
7769                         fprintf(debugFP,
7770                                 "PV is too long; using the first %u bytes.\n",
7771                                 (unsigned) sizeof(programStats.movelist) - 1);
7772                     }
7773
7774                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7775                 } else {
7776                     sprintf(programStats.movelist, " no PV\n");
7777                 }
7778
7779                 if (programStats.seen_stat) {
7780                     programStats.ok_to_send = 1;
7781                 }
7782
7783                 if (strchr(programStats.movelist, '(') != NULL) {
7784                     programStats.line_is_book = 1;
7785                     programStats.nr_moves = 0;
7786                     programStats.moves_left = 0;
7787                 } else {
7788                     programStats.line_is_book = 0;
7789                 }
7790
7791                 SendProgramStatsToFrontend( cps, &programStats );
7792
7793                 /* 
7794                     [AS] Protect the thinkOutput buffer from overflow... this
7795                     is only useful if buf1 hasn't overflowed first!
7796                 */
7797                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7798                         plylev, 
7799                         (gameMode == TwoMachinesPlay ?
7800                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7801                         ((double) curscore) / 100.0,
7802                         prefixHint ? lastHint : "",
7803                         prefixHint ? " " : "" );
7804
7805                 if( buf1[0] != NULLCHAR ) {
7806                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7807
7808                     if( strlen(buf1) > max_len ) {
7809                         if( appData.debugMode) {
7810                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7811                         }
7812                         buf1[max_len+1] = '\0';
7813                     }
7814
7815                     strcat( thinkOutput, buf1 );
7816                 }
7817
7818                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7819                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7820                     DisplayMove(currentMove - 1);
7821                 }
7822                 return;
7823
7824             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7825                 /* crafty (9.25+) says "(only move) <move>"
7826                  * if there is only 1 legal move
7827                  */
7828                 sscanf(p, "(only move) %s", buf1);
7829                 sprintf(thinkOutput, "%s (only move)", buf1);
7830                 sprintf(programStats.movelist, "%s (only move)", buf1);
7831                 programStats.depth = 1;
7832                 programStats.nr_moves = 1;
7833                 programStats.moves_left = 1;
7834                 programStats.nodes = 1;
7835                 programStats.time = 1;
7836                 programStats.got_only_move = 1;
7837
7838                 /* Not really, but we also use this member to
7839                    mean "line isn't going to change" (Crafty
7840                    isn't searching, so stats won't change) */
7841                 programStats.line_is_book = 1;
7842
7843                 SendProgramStatsToFrontend( cps, &programStats );
7844                 
7845                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7846                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7847                     DisplayMove(currentMove - 1);
7848                 }
7849                 return;
7850             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7851                               &time, &nodes, &plylev, &mvleft,
7852                               &mvtot, mvname) >= 5) {
7853                 /* The stat01: line is from Crafty (9.29+) in response
7854                    to the "." command */
7855                 programStats.seen_stat = 1;
7856                 cps->maybeThinking = TRUE;
7857
7858                 if (programStats.got_only_move || !appData.periodicUpdates)
7859                   return;
7860
7861                 programStats.depth = plylev;
7862                 programStats.time = time;
7863                 programStats.nodes = nodes;
7864                 programStats.moves_left = mvleft;
7865                 programStats.nr_moves = mvtot;
7866                 strcpy(programStats.move_name, mvname);
7867                 programStats.ok_to_send = 1;
7868                 programStats.movelist[0] = '\0';
7869
7870                 SendProgramStatsToFrontend( cps, &programStats );
7871
7872                 return;
7873
7874             } else if (strncmp(message,"++",2) == 0) {
7875                 /* Crafty 9.29+ outputs this */
7876                 programStats.got_fail = 2;
7877                 return;
7878
7879             } else if (strncmp(message,"--",2) == 0) {
7880                 /* Crafty 9.29+ outputs this */
7881                 programStats.got_fail = 1;
7882                 return;
7883
7884             } else if (thinkOutput[0] != NULLCHAR &&
7885                        strncmp(message, "    ", 4) == 0) {
7886                 unsigned message_len;
7887
7888                 p = message;
7889                 while (*p && *p == ' ') p++;
7890
7891                 message_len = strlen( p );
7892
7893                 /* [AS] Avoid buffer overflow */
7894                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7895                     strcat(thinkOutput, " ");
7896                     strcat(thinkOutput, p);
7897                 }
7898
7899                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7900                     strcat(programStats.movelist, " ");
7901                     strcat(programStats.movelist, p);
7902                 }
7903
7904                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7905                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7906                     DisplayMove(currentMove - 1);
7907                 }
7908                 return;
7909             }
7910         }
7911         else {
7912             buf1[0] = NULLCHAR;
7913
7914             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7915                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7916             {
7917                 ChessProgramStats cpstats;
7918
7919                 if (plyext != ' ' && plyext != '\t') {
7920                     time *= 100;
7921                 }
7922
7923                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7924                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7925                     curscore = -curscore;
7926                 }
7927
7928                 cpstats.depth = plylev;
7929                 cpstats.nodes = nodes;
7930                 cpstats.time = time;
7931                 cpstats.score = curscore;
7932                 cpstats.got_only_move = 0;
7933                 cpstats.movelist[0] = '\0';
7934
7935                 if (buf1[0] != NULLCHAR) {
7936                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7937                 }
7938
7939                 cpstats.ok_to_send = 0;
7940                 cpstats.line_is_book = 0;
7941                 cpstats.nr_moves = 0;
7942                 cpstats.moves_left = 0;
7943
7944                 SendProgramStatsToFrontend( cps, &cpstats );
7945             }
7946         }
7947     }
7948 }
7949
7950
7951 /* Parse a game score from the character string "game", and
7952    record it as the history of the current game.  The game
7953    score is NOT assumed to start from the standard position. 
7954    The display is not updated in any way.
7955    */
7956 void
7957 ParseGameHistory(game)
7958      char *game;
7959 {
7960     ChessMove moveType;
7961     int fromX, fromY, toX, toY, boardIndex;
7962     char promoChar;
7963     char *p, *q;
7964     char buf[MSG_SIZ];
7965
7966     if (appData.debugMode)
7967       fprintf(debugFP, "Parsing game history: %s\n", game);
7968
7969     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7970     gameInfo.site = StrSave(appData.icsHost);
7971     gameInfo.date = PGNDate();
7972     gameInfo.round = StrSave("-");
7973
7974     /* Parse out names of players */
7975     while (*game == ' ') game++;
7976     p = buf;
7977     while (*game != ' ') *p++ = *game++;
7978     *p = NULLCHAR;
7979     gameInfo.white = StrSave(buf);
7980     while (*game == ' ') game++;
7981     p = buf;
7982     while (*game != ' ' && *game != '\n') *p++ = *game++;
7983     *p = NULLCHAR;
7984     gameInfo.black = StrSave(buf);
7985
7986     /* Parse moves */
7987     boardIndex = blackPlaysFirst ? 1 : 0;
7988     yynewstr(game);
7989     for (;;) {
7990         yyboardindex = boardIndex;
7991         moveType = (ChessMove) yylex();
7992         switch (moveType) {
7993           case IllegalMove:             /* maybe suicide chess, etc. */
7994   if (appData.debugMode) {
7995     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7996     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7997     setbuf(debugFP, NULL);
7998   }
7999           case WhitePromotionChancellor:
8000           case BlackPromotionChancellor:
8001           case WhitePromotionArchbishop:
8002           case BlackPromotionArchbishop:
8003           case WhitePromotionQueen:
8004           case BlackPromotionQueen:
8005           case WhitePromotionRook:
8006           case BlackPromotionRook:
8007           case WhitePromotionBishop:
8008           case BlackPromotionBishop:
8009           case WhitePromotionKnight:
8010           case BlackPromotionKnight:
8011           case WhitePromotionKing:
8012           case BlackPromotionKing:
8013           case NormalMove:
8014           case WhiteCapturesEnPassant:
8015           case BlackCapturesEnPassant:
8016           case WhiteKingSideCastle:
8017           case WhiteQueenSideCastle:
8018           case BlackKingSideCastle:
8019           case BlackQueenSideCastle:
8020           case WhiteKingSideCastleWild:
8021           case WhiteQueenSideCastleWild:
8022           case BlackKingSideCastleWild:
8023           case BlackQueenSideCastleWild:
8024           /* PUSH Fabien */
8025           case WhiteHSideCastleFR:
8026           case WhiteASideCastleFR:
8027           case BlackHSideCastleFR:
8028           case BlackASideCastleFR:
8029           /* POP Fabien */
8030             fromX = currentMoveString[0] - AAA;
8031             fromY = currentMoveString[1] - ONE;
8032             toX = currentMoveString[2] - AAA;
8033             toY = currentMoveString[3] - ONE;
8034             promoChar = currentMoveString[4];
8035             break;
8036           case WhiteDrop:
8037           case BlackDrop:
8038             fromX = moveType == WhiteDrop ?
8039               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8040             (int) CharToPiece(ToLower(currentMoveString[0]));
8041             fromY = DROP_RANK;
8042             toX = currentMoveString[2] - AAA;
8043             toY = currentMoveString[3] - ONE;
8044             promoChar = NULLCHAR;
8045             break;
8046           case AmbiguousMove:
8047             /* bug? */
8048             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8049   if (appData.debugMode) {
8050     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8051     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8052     setbuf(debugFP, NULL);
8053   }
8054             DisplayError(buf, 0);
8055             return;
8056           case ImpossibleMove:
8057             /* bug? */
8058             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8059   if (appData.debugMode) {
8060     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8061     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8062     setbuf(debugFP, NULL);
8063   }
8064             DisplayError(buf, 0);
8065             return;
8066           case (ChessMove) 0:   /* end of file */
8067             if (boardIndex < backwardMostMove) {
8068                 /* Oops, gap.  How did that happen? */
8069                 DisplayError(_("Gap in move list"), 0);
8070                 return;
8071             }
8072             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8073             if (boardIndex > forwardMostMove) {
8074                 forwardMostMove = boardIndex;
8075             }
8076             return;
8077           case ElapsedTime:
8078             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8079                 strcat(parseList[boardIndex-1], " ");
8080                 strcat(parseList[boardIndex-1], yy_text);
8081             }
8082             continue;
8083           case Comment:
8084           case PGNTag:
8085           case NAG:
8086           default:
8087             /* ignore */
8088             continue;
8089           case WhiteWins:
8090           case BlackWins:
8091           case GameIsDrawn:
8092           case GameUnfinished:
8093             if (gameMode == IcsExamining) {
8094                 if (boardIndex < backwardMostMove) {
8095                     /* Oops, gap.  How did that happen? */
8096                     return;
8097                 }
8098                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8099                 return;
8100             }
8101             gameInfo.result = moveType;
8102             p = strchr(yy_text, '{');
8103             if (p == NULL) p = strchr(yy_text, '(');
8104             if (p == NULL) {
8105                 p = yy_text;
8106                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8107             } else {
8108                 q = strchr(p, *p == '{' ? '}' : ')');
8109                 if (q != NULL) *q = NULLCHAR;
8110                 p++;
8111             }
8112             gameInfo.resultDetails = StrSave(p);
8113             continue;
8114         }
8115         if (boardIndex >= forwardMostMove &&
8116             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8117             backwardMostMove = blackPlaysFirst ? 1 : 0;
8118             return;
8119         }
8120         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8121                                  fromY, fromX, toY, toX, promoChar,
8122                                  parseList[boardIndex]);
8123         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8124         /* currentMoveString is set as a side-effect of yylex */
8125         strcpy(moveList[boardIndex], currentMoveString);
8126         strcat(moveList[boardIndex], "\n");
8127         boardIndex++;
8128         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8129         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8130           case MT_NONE:
8131           case MT_STALEMATE:
8132           default:
8133             break;
8134           case MT_CHECK:
8135             if(gameInfo.variant != VariantShogi)
8136                 strcat(parseList[boardIndex - 1], "+");
8137             break;
8138           case MT_CHECKMATE:
8139           case MT_STAINMATE:
8140             strcat(parseList[boardIndex - 1], "#");
8141             break;
8142         }
8143     }
8144 }
8145
8146
8147 /* Apply a move to the given board  */
8148 void
8149 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8150      int fromX, fromY, toX, toY;
8151      int promoChar;
8152      Board board;
8153 {
8154   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8155   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8156
8157     /* [HGM] compute & store e.p. status and castling rights for new position */
8158     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8159     { int i;
8160
8161       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8162       oldEP = (signed char)board[EP_STATUS];
8163       board[EP_STATUS] = EP_NONE;
8164
8165       if( board[toY][toX] != EmptySquare ) 
8166            board[EP_STATUS] = EP_CAPTURE;  
8167
8168       if( board[fromY][fromX] == WhitePawn ) {
8169            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8170                board[EP_STATUS] = EP_PAWN_MOVE;
8171            if( toY-fromY==2) {
8172                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8173                         gameInfo.variant != VariantBerolina || toX < fromX)
8174                       board[EP_STATUS] = toX | berolina;
8175                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8176                         gameInfo.variant != VariantBerolina || toX > fromX) 
8177                       board[EP_STATUS] = toX;
8178            }
8179       } else 
8180       if( board[fromY][fromX] == BlackPawn ) {
8181            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8182                board[EP_STATUS] = EP_PAWN_MOVE; 
8183            if( toY-fromY== -2) {
8184                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8185                         gameInfo.variant != VariantBerolina || toX < fromX)
8186                       board[EP_STATUS] = toX | berolina;
8187                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8188                         gameInfo.variant != VariantBerolina || toX > fromX) 
8189                       board[EP_STATUS] = toX;
8190            }
8191        }
8192
8193        for(i=0; i<nrCastlingRights; i++) {
8194            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8195               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8196              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8197        }
8198
8199     }
8200
8201   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8202   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8203        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8204          
8205   if (fromX == toX && fromY == toY) return;
8206
8207   if (fromY == DROP_RANK) {
8208         /* must be first */
8209         piece = board[toY][toX] = (ChessSquare) fromX;
8210   } else {
8211      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8212      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8213      if(gameInfo.variant == VariantKnightmate)
8214          king += (int) WhiteUnicorn - (int) WhiteKing;
8215
8216     /* Code added by Tord: */
8217     /* FRC castling assumed when king captures friendly rook. */
8218     if (board[fromY][fromX] == WhiteKing &&
8219              board[toY][toX] == WhiteRook) {
8220       board[fromY][fromX] = EmptySquare;
8221       board[toY][toX] = EmptySquare;
8222       if(toX > fromX) {
8223         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8224       } else {
8225         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8226       }
8227     } else if (board[fromY][fromX] == BlackKing &&
8228                board[toY][toX] == BlackRook) {
8229       board[fromY][fromX] = EmptySquare;
8230       board[toY][toX] = EmptySquare;
8231       if(toX > fromX) {
8232         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8233       } else {
8234         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8235       }
8236     /* End of code added by Tord */
8237
8238     } else if (board[fromY][fromX] == king
8239         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8240         && toY == fromY && toX > fromX+1) {
8241         board[fromY][fromX] = EmptySquare;
8242         board[toY][toX] = king;
8243         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8244         board[fromY][BOARD_RGHT-1] = EmptySquare;
8245     } else if (board[fromY][fromX] == king
8246         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8247                && toY == fromY && toX < fromX-1) {
8248         board[fromY][fromX] = EmptySquare;
8249         board[toY][toX] = king;
8250         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8251         board[fromY][BOARD_LEFT] = EmptySquare;
8252     } else if (board[fromY][fromX] == WhitePawn
8253                && toY >= BOARD_HEIGHT-promoRank
8254                && gameInfo.variant != VariantXiangqi
8255                ) {
8256         /* white pawn promotion */
8257         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8258         if (board[toY][toX] == EmptySquare) {
8259             board[toY][toX] = WhiteQueen;
8260         }
8261         if(gameInfo.variant==VariantBughouse ||
8262            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8263             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8264         board[fromY][fromX] = EmptySquare;
8265     } else if ((fromY == BOARD_HEIGHT-4)
8266                && (toX != fromX)
8267                && gameInfo.variant != VariantXiangqi
8268                && gameInfo.variant != VariantBerolina
8269                && (board[fromY][fromX] == WhitePawn)
8270                && (board[toY][toX] == EmptySquare)) {
8271         board[fromY][fromX] = EmptySquare;
8272         board[toY][toX] = WhitePawn;
8273         captured = board[toY - 1][toX];
8274         board[toY - 1][toX] = EmptySquare;
8275     } else if ((fromY == BOARD_HEIGHT-4)
8276                && (toX == fromX)
8277                && gameInfo.variant == VariantBerolina
8278                && (board[fromY][fromX] == WhitePawn)
8279                && (board[toY][toX] == EmptySquare)) {
8280         board[fromY][fromX] = EmptySquare;
8281         board[toY][toX] = WhitePawn;
8282         if(oldEP & EP_BEROLIN_A) {
8283                 captured = board[fromY][fromX-1];
8284                 board[fromY][fromX-1] = EmptySquare;
8285         }else{  captured = board[fromY][fromX+1];
8286                 board[fromY][fromX+1] = EmptySquare;
8287         }
8288     } else if (board[fromY][fromX] == king
8289         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8290                && toY == fromY && toX > fromX+1) {
8291         board[fromY][fromX] = EmptySquare;
8292         board[toY][toX] = king;
8293         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8294         board[fromY][BOARD_RGHT-1] = EmptySquare;
8295     } else if (board[fromY][fromX] == king
8296         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8297                && toY == fromY && toX < fromX-1) {
8298         board[fromY][fromX] = EmptySquare;
8299         board[toY][toX] = king;
8300         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8301         board[fromY][BOARD_LEFT] = EmptySquare;
8302     } else if (fromY == 7 && fromX == 3
8303                && board[fromY][fromX] == BlackKing
8304                && toY == 7 && toX == 5) {
8305         board[fromY][fromX] = EmptySquare;
8306         board[toY][toX] = BlackKing;
8307         board[fromY][7] = EmptySquare;
8308         board[toY][4] = BlackRook;
8309     } else if (fromY == 7 && fromX == 3
8310                && board[fromY][fromX] == BlackKing
8311                && toY == 7 && toX == 1) {
8312         board[fromY][fromX] = EmptySquare;
8313         board[toY][toX] = BlackKing;
8314         board[fromY][0] = EmptySquare;
8315         board[toY][2] = BlackRook;
8316     } else if (board[fromY][fromX] == BlackPawn
8317                && toY < promoRank
8318                && gameInfo.variant != VariantXiangqi
8319                ) {
8320         /* black pawn promotion */
8321         board[toY][toX] = CharToPiece(ToLower(promoChar));
8322         if (board[toY][toX] == EmptySquare) {
8323             board[toY][toX] = BlackQueen;
8324         }
8325         if(gameInfo.variant==VariantBughouse ||
8326            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8327             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8328         board[fromY][fromX] = EmptySquare;
8329     } else if ((fromY == 3)
8330                && (toX != fromX)
8331                && gameInfo.variant != VariantXiangqi
8332                && gameInfo.variant != VariantBerolina
8333                && (board[fromY][fromX] == BlackPawn)
8334                && (board[toY][toX] == EmptySquare)) {
8335         board[fromY][fromX] = EmptySquare;
8336         board[toY][toX] = BlackPawn;
8337         captured = board[toY + 1][toX];
8338         board[toY + 1][toX] = EmptySquare;
8339     } else if ((fromY == 3)
8340                && (toX == fromX)
8341                && gameInfo.variant == VariantBerolina
8342                && (board[fromY][fromX] == BlackPawn)
8343                && (board[toY][toX] == EmptySquare)) {
8344         board[fromY][fromX] = EmptySquare;
8345         board[toY][toX] = BlackPawn;
8346         if(oldEP & EP_BEROLIN_A) {
8347                 captured = board[fromY][fromX-1];
8348                 board[fromY][fromX-1] = EmptySquare;
8349         }else{  captured = board[fromY][fromX+1];
8350                 board[fromY][fromX+1] = EmptySquare;
8351         }
8352     } else {
8353         board[toY][toX] = board[fromY][fromX];
8354         board[fromY][fromX] = EmptySquare;
8355     }
8356
8357     /* [HGM] now we promote for Shogi, if needed */
8358     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8359         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8360   }
8361
8362     if (gameInfo.holdingsWidth != 0) {
8363
8364       /* !!A lot more code needs to be written to support holdings  */
8365       /* [HGM] OK, so I have written it. Holdings are stored in the */
8366       /* penultimate board files, so they are automaticlly stored   */
8367       /* in the game history.                                       */
8368       if (fromY == DROP_RANK) {
8369         /* Delete from holdings, by decreasing count */
8370         /* and erasing image if necessary            */
8371         p = (int) fromX;
8372         if(p < (int) BlackPawn) { /* white drop */
8373              p -= (int)WhitePawn;
8374                  p = PieceToNumber((ChessSquare)p);
8375              if(p >= gameInfo.holdingsSize) p = 0;
8376              if(--board[p][BOARD_WIDTH-2] <= 0)
8377                   board[p][BOARD_WIDTH-1] = EmptySquare;
8378              if((int)board[p][BOARD_WIDTH-2] < 0)
8379                         board[p][BOARD_WIDTH-2] = 0;
8380         } else {                  /* black drop */
8381              p -= (int)BlackPawn;
8382                  p = PieceToNumber((ChessSquare)p);
8383              if(p >= gameInfo.holdingsSize) p = 0;
8384              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8385                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8386              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8387                         board[BOARD_HEIGHT-1-p][1] = 0;
8388         }
8389       }
8390       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8391           && gameInfo.variant != VariantBughouse        ) {
8392         /* [HGM] holdings: Add to holdings, if holdings exist */
8393         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8394                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8395                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8396         }
8397         p = (int) captured;
8398         if (p >= (int) BlackPawn) {
8399           p -= (int)BlackPawn;
8400           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8401                   /* in Shogi restore piece to its original  first */
8402                   captured = (ChessSquare) (DEMOTED captured);
8403                   p = DEMOTED p;
8404           }
8405           p = PieceToNumber((ChessSquare)p);
8406           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8407           board[p][BOARD_WIDTH-2]++;
8408           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8409         } else {
8410           p -= (int)WhitePawn;
8411           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8412                   captured = (ChessSquare) (DEMOTED captured);
8413                   p = DEMOTED p;
8414           }
8415           p = PieceToNumber((ChessSquare)p);
8416           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8417           board[BOARD_HEIGHT-1-p][1]++;
8418           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8419         }
8420       }
8421     } else if (gameInfo.variant == VariantAtomic) {
8422       if (captured != EmptySquare) {
8423         int y, x;
8424         for (y = toY-1; y <= toY+1; y++) {
8425           for (x = toX-1; x <= toX+1; x++) {
8426             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8427                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8428               board[y][x] = EmptySquare;
8429             }
8430           }
8431         }
8432         board[toY][toX] = EmptySquare;
8433       }
8434     }
8435     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8436         /* [HGM] Shogi promotions */
8437         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8438     }
8439
8440     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8441                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8442         // [HGM] superchess: take promotion piece out of holdings
8443         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8444         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8445             if(!--board[k][BOARD_WIDTH-2])
8446                 board[k][BOARD_WIDTH-1] = EmptySquare;
8447         } else {
8448             if(!--board[BOARD_HEIGHT-1-k][1])
8449                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8450         }
8451     }
8452
8453 }
8454
8455 /* Updates forwardMostMove */
8456 void
8457 MakeMove(fromX, fromY, toX, toY, promoChar)
8458      int fromX, fromY, toX, toY;
8459      int promoChar;
8460 {
8461 //    forwardMostMove++; // [HGM] bare: moved downstream
8462
8463     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8464         int timeLeft; static int lastLoadFlag=0; int king, piece;
8465         piece = boards[forwardMostMove][fromY][fromX];
8466         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8467         if(gameInfo.variant == VariantKnightmate)
8468             king += (int) WhiteUnicorn - (int) WhiteKing;
8469         if(forwardMostMove == 0) {
8470             if(blackPlaysFirst) 
8471                 fprintf(serverMoves, "%s;", second.tidy);
8472             fprintf(serverMoves, "%s;", first.tidy);
8473             if(!blackPlaysFirst) 
8474                 fprintf(serverMoves, "%s;", second.tidy);
8475         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8476         lastLoadFlag = loadFlag;
8477         // print base move
8478         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8479         // print castling suffix
8480         if( toY == fromY && piece == king ) {
8481             if(toX-fromX > 1)
8482                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8483             if(fromX-toX >1)
8484                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8485         }
8486         // e.p. suffix
8487         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8488              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8489              boards[forwardMostMove][toY][toX] == EmptySquare
8490              && fromX != toX )
8491                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8492         // promotion suffix
8493         if(promoChar != NULLCHAR)
8494                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8495         if(!loadFlag) {
8496             fprintf(serverMoves, "/%d/%d",
8497                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8498             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8499             else                      timeLeft = blackTimeRemaining/1000;
8500             fprintf(serverMoves, "/%d", timeLeft);
8501         }
8502         fflush(serverMoves);
8503     }
8504
8505     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8506       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8507                         0, 1);
8508       return;
8509     }
8510     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8511     if (commentList[forwardMostMove+1] != NULL) {
8512         free(commentList[forwardMostMove+1]);
8513         commentList[forwardMostMove+1] = NULL;
8514     }
8515     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8516     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8517     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8518     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8519     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8520     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8521     gameInfo.result = GameUnfinished;
8522     if (gameInfo.resultDetails != NULL) {
8523         free(gameInfo.resultDetails);
8524         gameInfo.resultDetails = NULL;
8525     }
8526     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8527                               moveList[forwardMostMove - 1]);
8528     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8529                              PosFlags(forwardMostMove - 1),
8530                              fromY, fromX, toY, toX, promoChar,
8531                              parseList[forwardMostMove - 1]);
8532     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8533       case MT_NONE:
8534       case MT_STALEMATE:
8535       default:
8536         break;
8537       case MT_CHECK:
8538         if(gameInfo.variant != VariantShogi)
8539             strcat(parseList[forwardMostMove - 1], "+");
8540         break;
8541       case MT_CHECKMATE:
8542       case MT_STAINMATE:
8543         strcat(parseList[forwardMostMove - 1], "#");
8544         break;
8545     }
8546     if (appData.debugMode) {
8547         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8548     }
8549
8550 }
8551
8552 /* Updates currentMove if not pausing */
8553 void
8554 ShowMove(fromX, fromY, toX, toY)
8555 {
8556     int instant = (gameMode == PlayFromGameFile) ?
8557         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8558     if(appData.noGUI) return;
8559     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8560         if (!instant) {
8561             if (forwardMostMove == currentMove + 1) {
8562                 AnimateMove(boards[forwardMostMove - 1],
8563                             fromX, fromY, toX, toY);
8564             }
8565             if (appData.highlightLastMove) {
8566                 SetHighlights(fromX, fromY, toX, toY);
8567             }
8568         }
8569         currentMove = forwardMostMove;
8570     }
8571
8572     if (instant) return;
8573
8574     DisplayMove(currentMove - 1);
8575     DrawPosition(FALSE, boards[currentMove]);
8576     DisplayBothClocks();
8577     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8578 }
8579
8580 void SendEgtPath(ChessProgramState *cps)
8581 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8582         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8583
8584         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8585
8586         while(*p) {
8587             char c, *q = name+1, *r, *s;
8588
8589             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8590             while(*p && *p != ',') *q++ = *p++;
8591             *q++ = ':'; *q = 0;
8592             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8593                 strcmp(name, ",nalimov:") == 0 ) {
8594                 // take nalimov path from the menu-changeable option first, if it is defined
8595                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8596                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8597             } else
8598             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8599                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8600                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8601                 s = r = StrStr(s, ":") + 1; // beginning of path info
8602                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8603                 c = *r; *r = 0;             // temporarily null-terminate path info
8604                     *--q = 0;               // strip of trailig ':' from name
8605                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8606                 *r = c;
8607                 SendToProgram(buf,cps);     // send egtbpath command for this format
8608             }
8609             if(*p == ',') p++; // read away comma to position for next format name
8610         }
8611 }
8612
8613 void
8614 InitChessProgram(cps, setup)
8615      ChessProgramState *cps;
8616      int setup; /* [HGM] needed to setup FRC opening position */
8617 {
8618     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8619     if (appData.noChessProgram) return;
8620     hintRequested = FALSE;
8621     bookRequested = FALSE;
8622
8623     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8624     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8625     if(cps->memSize) { /* [HGM] memory */
8626         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8627         SendToProgram(buf, cps);
8628     }
8629     SendEgtPath(cps); /* [HGM] EGT */
8630     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8631         sprintf(buf, "cores %d\n", appData.smpCores);
8632         SendToProgram(buf, cps);
8633     }
8634
8635     SendToProgram(cps->initString, cps);
8636     if (gameInfo.variant != VariantNormal &&
8637         gameInfo.variant != VariantLoadable
8638         /* [HGM] also send variant if board size non-standard */
8639         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8640                                             ) {
8641       char *v = VariantName(gameInfo.variant);
8642       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8643         /* [HGM] in protocol 1 we have to assume all variants valid */
8644         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8645         DisplayFatalError(buf, 0, 1);
8646         return;
8647       }
8648
8649       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8650       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8651       if( gameInfo.variant == VariantXiangqi )
8652            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8653       if( gameInfo.variant == VariantShogi )
8654            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8655       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8656            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8657       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8658                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8659            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8660       if( gameInfo.variant == VariantCourier )
8661            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8662       if( gameInfo.variant == VariantSuper )
8663            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8664       if( gameInfo.variant == VariantGreat )
8665            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8666
8667       if(overruled) {
8668            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8669                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8670            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8671            if(StrStr(cps->variants, b) == NULL) { 
8672                // specific sized variant not known, check if general sizing allowed
8673                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8674                    if(StrStr(cps->variants, "boardsize") == NULL) {
8675                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8676                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8677                        DisplayFatalError(buf, 0, 1);
8678                        return;
8679                    }
8680                    /* [HGM] here we really should compare with the maximum supported board size */
8681                }
8682            }
8683       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8684       sprintf(buf, "variant %s\n", b);
8685       SendToProgram(buf, cps);
8686     }
8687     currentlyInitializedVariant = gameInfo.variant;
8688
8689     /* [HGM] send opening position in FRC to first engine */
8690     if(setup) {
8691           SendToProgram("force\n", cps);
8692           SendBoard(cps, 0);
8693           /* engine is now in force mode! Set flag to wake it up after first move. */
8694           setboardSpoiledMachineBlack = 1;
8695     }
8696
8697     if (cps->sendICS) {
8698       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8699       SendToProgram(buf, cps);
8700     }
8701     cps->maybeThinking = FALSE;
8702     cps->offeredDraw = 0;
8703     if (!appData.icsActive) {
8704         SendTimeControl(cps, movesPerSession, timeControl,
8705                         timeIncrement, appData.searchDepth,
8706                         searchTime);
8707     }
8708     if (appData.showThinking 
8709         // [HGM] thinking: four options require thinking output to be sent
8710         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8711                                 ) {
8712         SendToProgram("post\n", cps);
8713     }
8714     SendToProgram("hard\n", cps);
8715     if (!appData.ponderNextMove) {
8716         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8717            it without being sure what state we are in first.  "hard"
8718            is not a toggle, so that one is OK.
8719          */
8720         SendToProgram("easy\n", cps);
8721     }
8722     if (cps->usePing) {
8723       sprintf(buf, "ping %d\n", ++cps->lastPing);
8724       SendToProgram(buf, cps);
8725     }
8726     cps->initDone = TRUE;
8727 }   
8728
8729
8730 void
8731 StartChessProgram(cps)
8732      ChessProgramState *cps;
8733 {
8734     char buf[MSG_SIZ];
8735     int err;
8736
8737     if (appData.noChessProgram) return;
8738     cps->initDone = FALSE;
8739
8740     if (strcmp(cps->host, "localhost") == 0) {
8741         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8742     } else if (*appData.remoteShell == NULLCHAR) {
8743         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8744     } else {
8745         if (*appData.remoteUser == NULLCHAR) {
8746           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8747                     cps->program);
8748         } else {
8749           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8750                     cps->host, appData.remoteUser, cps->program);
8751         }
8752         err = StartChildProcess(buf, "", &cps->pr);
8753     }
8754     
8755     if (err != 0) {
8756         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8757         DisplayFatalError(buf, err, 1);
8758         cps->pr = NoProc;
8759         cps->isr = NULL;
8760         return;
8761     }
8762     
8763     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8764     if (cps->protocolVersion > 1) {
8765       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8766       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8767       cps->comboCnt = 0;  //                and values of combo boxes
8768       SendToProgram(buf, cps);
8769     } else {
8770       SendToProgram("xboard\n", cps);
8771     }
8772 }
8773
8774
8775 void
8776 TwoMachinesEventIfReady P((void))
8777 {
8778   if (first.lastPing != first.lastPong) {
8779     DisplayMessage("", _("Waiting for first chess program"));
8780     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8781     return;
8782   }
8783   if (second.lastPing != second.lastPong) {
8784     DisplayMessage("", _("Waiting for second chess program"));
8785     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8786     return;
8787   }
8788   ThawUI();
8789   TwoMachinesEvent();
8790 }
8791
8792 void
8793 NextMatchGame P((void))
8794 {
8795     int index; /* [HGM] autoinc: step load index during match */
8796     Reset(FALSE, TRUE);
8797     if (*appData.loadGameFile != NULLCHAR) {
8798         index = appData.loadGameIndex;
8799         if(index < 0) { // [HGM] autoinc
8800             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8801             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8802         } 
8803         LoadGameFromFile(appData.loadGameFile,
8804                          index,
8805                          appData.loadGameFile, FALSE);
8806     } else if (*appData.loadPositionFile != NULLCHAR) {
8807         index = appData.loadPositionIndex;
8808         if(index < 0) { // [HGM] autoinc
8809             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8810             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8811         } 
8812         LoadPositionFromFile(appData.loadPositionFile,
8813                              index,
8814                              appData.loadPositionFile);
8815     }
8816     TwoMachinesEventIfReady();
8817 }
8818
8819 void UserAdjudicationEvent( int result )
8820 {
8821     ChessMove gameResult = GameIsDrawn;
8822
8823     if( result > 0 ) {
8824         gameResult = WhiteWins;
8825     }
8826     else if( result < 0 ) {
8827         gameResult = BlackWins;
8828     }
8829
8830     if( gameMode == TwoMachinesPlay ) {
8831         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8832     }
8833 }
8834
8835
8836 // [HGM] save: calculate checksum of game to make games easily identifiable
8837 int StringCheckSum(char *s)
8838 {
8839         int i = 0;
8840         if(s==NULL) return 0;
8841         while(*s) i = i*259 + *s++;
8842         return i;
8843 }
8844
8845 int GameCheckSum()
8846 {
8847         int i, sum=0;
8848         for(i=backwardMostMove; i<forwardMostMove; i++) {
8849                 sum += pvInfoList[i].depth;
8850                 sum += StringCheckSum(parseList[i]);
8851                 sum += StringCheckSum(commentList[i]);
8852                 sum *= 261;
8853         }
8854         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8855         return sum + StringCheckSum(commentList[i]);
8856 } // end of save patch
8857
8858 void
8859 GameEnds(result, resultDetails, whosays)
8860      ChessMove result;
8861      char *resultDetails;
8862      int whosays;
8863 {
8864     GameMode nextGameMode;
8865     int isIcsGame;
8866     char buf[MSG_SIZ];
8867
8868     if(endingGame) return; /* [HGM] crash: forbid recursion */
8869     endingGame = 1;
8870
8871     if (appData.debugMode) {
8872       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8873               result, resultDetails ? resultDetails : "(null)", whosays);
8874     }
8875
8876     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8877
8878     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8879         /* If we are playing on ICS, the server decides when the
8880            game is over, but the engine can offer to draw, claim 
8881            a draw, or resign. 
8882          */
8883 #if ZIPPY
8884         if (appData.zippyPlay && first.initDone) {
8885             if (result == GameIsDrawn) {
8886                 /* In case draw still needs to be claimed */
8887                 SendToICS(ics_prefix);
8888                 SendToICS("draw\n");
8889             } else if (StrCaseStr(resultDetails, "resign")) {
8890                 SendToICS(ics_prefix);
8891                 SendToICS("resign\n");
8892             }
8893         }
8894 #endif
8895         endingGame = 0; /* [HGM] crash */
8896         return;
8897     }
8898
8899     /* If we're loading the game from a file, stop */
8900     if (whosays == GE_FILE) {
8901       (void) StopLoadGameTimer();
8902       gameFileFP = NULL;
8903     }
8904
8905     /* Cancel draw offers */
8906     first.offeredDraw = second.offeredDraw = 0;
8907
8908     /* If this is an ICS game, only ICS can really say it's done;
8909        if not, anyone can. */
8910     isIcsGame = (gameMode == IcsPlayingWhite || 
8911                  gameMode == IcsPlayingBlack || 
8912                  gameMode == IcsObserving    || 
8913                  gameMode == IcsExamining);
8914
8915     if (!isIcsGame || whosays == GE_ICS) {
8916         /* OK -- not an ICS game, or ICS said it was done */
8917         StopClocks();
8918         if (!isIcsGame && !appData.noChessProgram) 
8919           SetUserThinkingEnables();
8920     
8921         /* [HGM] if a machine claims the game end we verify this claim */
8922         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8923             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8924                 char claimer;
8925                 ChessMove trueResult = (ChessMove) -1;
8926
8927                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8928                                             first.twoMachinesColor[0] :
8929                                             second.twoMachinesColor[0] ;
8930
8931                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8932                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8933                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8934                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8935                 } else
8936                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8937                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8938                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8939                 } else
8940                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8941                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8942                 }
8943
8944                 // now verify win claims, but not in drop games, as we don't understand those yet
8945                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8946                                                  || gameInfo.variant == VariantGreat) &&
8947                     (result == WhiteWins && claimer == 'w' ||
8948                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8949                       if (appData.debugMode) {
8950                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8951                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8952                       }
8953                       if(result != trueResult) {
8954                               sprintf(buf, "False win claim: '%s'", resultDetails);
8955                               result = claimer == 'w' ? BlackWins : WhiteWins;
8956                               resultDetails = buf;
8957                       }
8958                 } else
8959                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8960                     && (forwardMostMove <= backwardMostMove ||
8961                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8962                         (claimer=='b')==(forwardMostMove&1))
8963                                                                                   ) {
8964                       /* [HGM] verify: draws that were not flagged are false claims */
8965                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8966                       result = claimer == 'w' ? BlackWins : WhiteWins;
8967                       resultDetails = buf;
8968                 }
8969                 /* (Claiming a loss is accepted no questions asked!) */
8970             }
8971             /* [HGM] bare: don't allow bare King to win */
8972             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8973                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8974                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8975                && result != GameIsDrawn)
8976             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8977                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8978                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8979                         if(p >= 0 && p <= (int)WhiteKing) k++;
8980                 }
8981                 if (appData.debugMode) {
8982                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8983                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8984                 }
8985                 if(k <= 1) {
8986                         result = GameIsDrawn;
8987                         sprintf(buf, "%s but bare king", resultDetails);
8988                         resultDetails = buf;
8989                 }
8990             }
8991         }
8992
8993
8994         if(serverMoves != NULL && !loadFlag) { char c = '=';
8995             if(result==WhiteWins) c = '+';
8996             if(result==BlackWins) c = '-';
8997             if(resultDetails != NULL)
8998                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8999         }
9000         if (resultDetails != NULL) {
9001             gameInfo.result = result;
9002             gameInfo.resultDetails = StrSave(resultDetails);
9003
9004             /* display last move only if game was not loaded from file */
9005             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9006                 DisplayMove(currentMove - 1);
9007     
9008             if (forwardMostMove != 0) {
9009                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9010                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9011                                                                 ) {
9012                     if (*appData.saveGameFile != NULLCHAR) {
9013                         SaveGameToFile(appData.saveGameFile, TRUE);
9014                     } else if (appData.autoSaveGames) {
9015                         AutoSaveGame();
9016                     }
9017                     if (*appData.savePositionFile != NULLCHAR) {
9018                         SavePositionToFile(appData.savePositionFile);
9019                     }
9020                 }
9021             }
9022
9023             /* Tell program how game ended in case it is learning */
9024             /* [HGM] Moved this to after saving the PGN, just in case */
9025             /* engine died and we got here through time loss. In that */
9026             /* case we will get a fatal error writing the pipe, which */
9027             /* would otherwise lose us the PGN.                       */
9028             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9029             /* output during GameEnds should never be fatal anymore   */
9030             if (gameMode == MachinePlaysWhite ||
9031                 gameMode == MachinePlaysBlack ||
9032                 gameMode == TwoMachinesPlay ||
9033                 gameMode == IcsPlayingWhite ||
9034                 gameMode == IcsPlayingBlack ||
9035                 gameMode == BeginningOfGame) {
9036                 char buf[MSG_SIZ];
9037                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9038                         resultDetails);
9039                 if (first.pr != NoProc) {
9040                     SendToProgram(buf, &first);
9041                 }
9042                 if (second.pr != NoProc &&
9043                     gameMode == TwoMachinesPlay) {
9044                     SendToProgram(buf, &second);
9045                 }
9046             }
9047         }
9048
9049         if (appData.icsActive) {
9050             if (appData.quietPlay &&
9051                 (gameMode == IcsPlayingWhite ||
9052                  gameMode == IcsPlayingBlack)) {
9053                 SendToICS(ics_prefix);
9054                 SendToICS("set shout 1\n");
9055             }
9056             nextGameMode = IcsIdle;
9057             ics_user_moved = FALSE;
9058             /* clean up premove.  It's ugly when the game has ended and the
9059              * premove highlights are still on the board.
9060              */
9061             if (gotPremove) {
9062               gotPremove = FALSE;
9063               ClearPremoveHighlights();
9064               DrawPosition(FALSE, boards[currentMove]);
9065             }
9066             if (whosays == GE_ICS) {
9067                 switch (result) {
9068                 case WhiteWins:
9069                     if (gameMode == IcsPlayingWhite)
9070                         PlayIcsWinSound();
9071                     else if(gameMode == IcsPlayingBlack)
9072                         PlayIcsLossSound();
9073                     break;
9074                 case BlackWins:
9075                     if (gameMode == IcsPlayingBlack)
9076                         PlayIcsWinSound();
9077                     else if(gameMode == IcsPlayingWhite)
9078                         PlayIcsLossSound();
9079                     break;
9080                 case GameIsDrawn:
9081                     PlayIcsDrawSound();
9082                     break;
9083                 default:
9084                     PlayIcsUnfinishedSound();
9085                 }
9086             }
9087         } else if (gameMode == EditGame ||
9088                    gameMode == PlayFromGameFile || 
9089                    gameMode == AnalyzeMode || 
9090                    gameMode == AnalyzeFile) {
9091             nextGameMode = gameMode;
9092         } else {
9093             nextGameMode = EndOfGame;
9094         }
9095         pausing = FALSE;
9096         ModeHighlight();
9097     } else {
9098         nextGameMode = gameMode;
9099     }
9100
9101     if (appData.noChessProgram) {
9102         gameMode = nextGameMode;
9103         ModeHighlight();
9104         endingGame = 0; /* [HGM] crash */
9105         return;
9106     }
9107
9108     if (first.reuse) {
9109         /* Put first chess program into idle state */
9110         if (first.pr != NoProc &&
9111             (gameMode == MachinePlaysWhite ||
9112              gameMode == MachinePlaysBlack ||
9113              gameMode == TwoMachinesPlay ||
9114              gameMode == IcsPlayingWhite ||
9115              gameMode == IcsPlayingBlack ||
9116              gameMode == BeginningOfGame)) {
9117             SendToProgram("force\n", &first);
9118             if (first.usePing) {
9119               char buf[MSG_SIZ];
9120               sprintf(buf, "ping %d\n", ++first.lastPing);
9121               SendToProgram(buf, &first);
9122             }
9123         }
9124     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9125         /* Kill off first chess program */
9126         if (first.isr != NULL)
9127           RemoveInputSource(first.isr);
9128         first.isr = NULL;
9129     
9130         if (first.pr != NoProc) {
9131             ExitAnalyzeMode();
9132             DoSleep( appData.delayBeforeQuit );
9133             SendToProgram("quit\n", &first);
9134             DoSleep( appData.delayAfterQuit );
9135             DestroyChildProcess(first.pr, first.useSigterm);
9136         }
9137         first.pr = NoProc;
9138     }
9139     if (second.reuse) {
9140         /* Put second chess program into idle state */
9141         if (second.pr != NoProc &&
9142             gameMode == TwoMachinesPlay) {
9143             SendToProgram("force\n", &second);
9144             if (second.usePing) {
9145               char buf[MSG_SIZ];
9146               sprintf(buf, "ping %d\n", ++second.lastPing);
9147               SendToProgram(buf, &second);
9148             }
9149         }
9150     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9151         /* Kill off second chess program */
9152         if (second.isr != NULL)
9153           RemoveInputSource(second.isr);
9154         second.isr = NULL;
9155     
9156         if (second.pr != NoProc) {
9157             DoSleep( appData.delayBeforeQuit );
9158             SendToProgram("quit\n", &second);
9159             DoSleep( appData.delayAfterQuit );
9160             DestroyChildProcess(second.pr, second.useSigterm);
9161         }
9162         second.pr = NoProc;
9163     }
9164
9165     if (matchMode && gameMode == TwoMachinesPlay) {
9166         switch (result) {
9167         case WhiteWins:
9168           if (first.twoMachinesColor[0] == 'w') {
9169             first.matchWins++;
9170           } else {
9171             second.matchWins++;
9172           }
9173           break;
9174         case BlackWins:
9175           if (first.twoMachinesColor[0] == 'b') {
9176             first.matchWins++;
9177           } else {
9178             second.matchWins++;
9179           }
9180           break;
9181         default:
9182           break;
9183         }
9184         if (matchGame < appData.matchGames) {
9185             char *tmp;
9186             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9187                 tmp = first.twoMachinesColor;
9188                 first.twoMachinesColor = second.twoMachinesColor;
9189                 second.twoMachinesColor = tmp;
9190             }
9191             gameMode = nextGameMode;
9192             matchGame++;
9193             if(appData.matchPause>10000 || appData.matchPause<10)
9194                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9195             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9196             endingGame = 0; /* [HGM] crash */
9197             return;
9198         } else {
9199             char buf[MSG_SIZ];
9200             gameMode = nextGameMode;
9201             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9202                     first.tidy, second.tidy,
9203                     first.matchWins, second.matchWins,
9204                     appData.matchGames - (first.matchWins + second.matchWins));
9205             DisplayFatalError(buf, 0, 0);
9206         }
9207     }
9208     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9209         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9210       ExitAnalyzeMode();
9211     gameMode = nextGameMode;
9212     ModeHighlight();
9213     endingGame = 0;  /* [HGM] crash */
9214 }
9215
9216 /* Assumes program was just initialized (initString sent).
9217    Leaves program in force mode. */
9218 void
9219 FeedMovesToProgram(cps, upto) 
9220      ChessProgramState *cps;
9221      int upto;
9222 {
9223     int i;
9224     
9225     if (appData.debugMode)
9226       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9227               startedFromSetupPosition ? "position and " : "",
9228               backwardMostMove, upto, cps->which);
9229     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9230         // [HGM] variantswitch: make engine aware of new variant
9231         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9232                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9233         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9234         SendToProgram(buf, cps);
9235         currentlyInitializedVariant = gameInfo.variant;
9236     }
9237     SendToProgram("force\n", cps);
9238     if (startedFromSetupPosition) {
9239         SendBoard(cps, backwardMostMove);
9240     if (appData.debugMode) {
9241         fprintf(debugFP, "feedMoves\n");
9242     }
9243     }
9244     for (i = backwardMostMove; i < upto; i++) {
9245         SendMoveToProgram(i, cps);
9246     }
9247 }
9248
9249
9250 void
9251 ResurrectChessProgram()
9252 {
9253      /* The chess program may have exited.
9254         If so, restart it and feed it all the moves made so far. */
9255
9256     if (appData.noChessProgram || first.pr != NoProc) return;
9257     
9258     StartChessProgram(&first);
9259     InitChessProgram(&first, FALSE);
9260     FeedMovesToProgram(&first, currentMove);
9261
9262     if (!first.sendTime) {
9263         /* can't tell gnuchess what its clock should read,
9264            so we bow to its notion. */
9265         ResetClocks();
9266         timeRemaining[0][currentMove] = whiteTimeRemaining;
9267         timeRemaining[1][currentMove] = blackTimeRemaining;
9268     }
9269
9270     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9271                 appData.icsEngineAnalyze) && first.analysisSupport) {
9272       SendToProgram("analyze\n", &first);
9273       first.analyzing = TRUE;
9274     }
9275 }
9276
9277 /*
9278  * Button procedures
9279  */
9280 void
9281 Reset(redraw, init)
9282      int redraw, init;
9283 {
9284     int i;
9285
9286     if (appData.debugMode) {
9287         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9288                 redraw, init, gameMode);
9289     }
9290     CleanupTail(); // [HGM] vari: delete any stored variations
9291     pausing = pauseExamInvalid = FALSE;
9292     startedFromSetupPosition = blackPlaysFirst = FALSE;
9293     firstMove = TRUE;
9294     whiteFlag = blackFlag = FALSE;
9295     userOfferedDraw = FALSE;
9296     hintRequested = bookRequested = FALSE;
9297     first.maybeThinking = FALSE;
9298     second.maybeThinking = FALSE;
9299     first.bookSuspend = FALSE; // [HGM] book
9300     second.bookSuspend = FALSE;
9301     thinkOutput[0] = NULLCHAR;
9302     lastHint[0] = NULLCHAR;
9303     ClearGameInfo(&gameInfo);
9304     gameInfo.variant = StringToVariant(appData.variant);
9305     ics_user_moved = ics_clock_paused = FALSE;
9306     ics_getting_history = H_FALSE;
9307     ics_gamenum = -1;
9308     white_holding[0] = black_holding[0] = NULLCHAR;
9309     ClearProgramStats();
9310     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9311     
9312     ResetFrontEnd();
9313     ClearHighlights();
9314     flipView = appData.flipView;
9315     ClearPremoveHighlights();
9316     gotPremove = FALSE;
9317     alarmSounded = FALSE;
9318
9319     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9320     if(appData.serverMovesName != NULL) {
9321         /* [HGM] prepare to make moves file for broadcasting */
9322         clock_t t = clock();
9323         if(serverMoves != NULL) fclose(serverMoves);
9324         serverMoves = fopen(appData.serverMovesName, "r");
9325         if(serverMoves != NULL) {
9326             fclose(serverMoves);
9327             /* delay 15 sec before overwriting, so all clients can see end */
9328             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9329         }
9330         serverMoves = fopen(appData.serverMovesName, "w");
9331     }
9332
9333     ExitAnalyzeMode();
9334     gameMode = BeginningOfGame;
9335     ModeHighlight();
9336     if(appData.icsActive) gameInfo.variant = VariantNormal;
9337     currentMove = forwardMostMove = backwardMostMove = 0;
9338     InitPosition(redraw);
9339     for (i = 0; i < MAX_MOVES; i++) {
9340         if (commentList[i] != NULL) {
9341             free(commentList[i]);
9342             commentList[i] = NULL;
9343         }
9344     }
9345     ResetClocks();
9346     timeRemaining[0][0] = whiteTimeRemaining;
9347     timeRemaining[1][0] = blackTimeRemaining;
9348     if (first.pr == NULL) {
9349         StartChessProgram(&first);
9350     }
9351     if (init) {
9352             InitChessProgram(&first, startedFromSetupPosition);
9353     }
9354     DisplayTitle("");
9355     DisplayMessage("", "");
9356     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9357     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9358 }
9359
9360 void
9361 AutoPlayGameLoop()
9362 {
9363     for (;;) {
9364         if (!AutoPlayOneMove())
9365           return;
9366         if (matchMode || appData.timeDelay == 0)
9367           continue;
9368         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9369           return;
9370         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9371         break;
9372     }
9373 }
9374
9375
9376 int
9377 AutoPlayOneMove()
9378 {
9379     int fromX, fromY, toX, toY;
9380
9381     if (appData.debugMode) {
9382       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9383     }
9384
9385     if (gameMode != PlayFromGameFile)
9386       return FALSE;
9387
9388     if (currentMove >= forwardMostMove) {
9389       gameMode = EditGame;
9390       ModeHighlight();
9391
9392       /* [AS] Clear current move marker at the end of a game */
9393       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9394
9395       return FALSE;
9396     }
9397     
9398     toX = moveList[currentMove][2] - AAA;
9399     toY = moveList[currentMove][3] - ONE;
9400
9401     if (moveList[currentMove][1] == '@') {
9402         if (appData.highlightLastMove) {
9403             SetHighlights(-1, -1, toX, toY);
9404         }
9405     } else {
9406         fromX = moveList[currentMove][0] - AAA;
9407         fromY = moveList[currentMove][1] - ONE;
9408
9409         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9410
9411         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9412
9413         if (appData.highlightLastMove) {
9414             SetHighlights(fromX, fromY, toX, toY);
9415         }
9416     }
9417     DisplayMove(currentMove);
9418     SendMoveToProgram(currentMove++, &first);
9419     DisplayBothClocks();
9420     DrawPosition(FALSE, boards[currentMove]);
9421     // [HGM] PV info: always display, routine tests if empty
9422     DisplayComment(currentMove - 1, commentList[currentMove]);
9423     return TRUE;
9424 }
9425
9426
9427 int
9428 LoadGameOneMove(readAhead)
9429      ChessMove readAhead;
9430 {
9431     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9432     char promoChar = NULLCHAR;
9433     ChessMove moveType;
9434     char move[MSG_SIZ];
9435     char *p, *q;
9436     
9437     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9438         gameMode != AnalyzeMode && gameMode != Training) {
9439         gameFileFP = NULL;
9440         return FALSE;
9441     }
9442     
9443     yyboardindex = forwardMostMove;
9444     if (readAhead != (ChessMove)0) {
9445       moveType = readAhead;
9446     } else {
9447       if (gameFileFP == NULL)
9448           return FALSE;
9449       moveType = (ChessMove) yylex();
9450     }
9451     
9452     done = FALSE;
9453     switch (moveType) {
9454       case Comment:
9455         if (appData.debugMode) 
9456           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9457         p = yy_text;
9458
9459         /* append the comment but don't display it */
9460         AppendComment(currentMove, p, FALSE);
9461         return TRUE;
9462
9463       case WhiteCapturesEnPassant:
9464       case BlackCapturesEnPassant:
9465       case WhitePromotionChancellor:
9466       case BlackPromotionChancellor:
9467       case WhitePromotionArchbishop:
9468       case BlackPromotionArchbishop:
9469       case WhitePromotionCentaur:
9470       case BlackPromotionCentaur:
9471       case WhitePromotionQueen:
9472       case BlackPromotionQueen:
9473       case WhitePromotionRook:
9474       case BlackPromotionRook:
9475       case WhitePromotionBishop:
9476       case BlackPromotionBishop:
9477       case WhitePromotionKnight:
9478       case BlackPromotionKnight:
9479       case WhitePromotionKing:
9480       case BlackPromotionKing:
9481       case NormalMove:
9482       case WhiteKingSideCastle:
9483       case WhiteQueenSideCastle:
9484       case BlackKingSideCastle:
9485       case BlackQueenSideCastle:
9486       case WhiteKingSideCastleWild:
9487       case WhiteQueenSideCastleWild:
9488       case BlackKingSideCastleWild:
9489       case BlackQueenSideCastleWild:
9490       /* PUSH Fabien */
9491       case WhiteHSideCastleFR:
9492       case WhiteASideCastleFR:
9493       case BlackHSideCastleFR:
9494       case BlackASideCastleFR:
9495       /* POP Fabien */
9496         if (appData.debugMode)
9497           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9498         fromX = currentMoveString[0] - AAA;
9499         fromY = currentMoveString[1] - ONE;
9500         toX = currentMoveString[2] - AAA;
9501         toY = currentMoveString[3] - ONE;
9502         promoChar = currentMoveString[4];
9503         break;
9504
9505       case WhiteDrop:
9506       case BlackDrop:
9507         if (appData.debugMode)
9508           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9509         fromX = moveType == WhiteDrop ?
9510           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9511         (int) CharToPiece(ToLower(currentMoveString[0]));
9512         fromY = DROP_RANK;
9513         toX = currentMoveString[2] - AAA;
9514         toY = currentMoveString[3] - ONE;
9515         break;
9516
9517       case WhiteWins:
9518       case BlackWins:
9519       case GameIsDrawn:
9520       case GameUnfinished:
9521         if (appData.debugMode)
9522           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9523         p = strchr(yy_text, '{');
9524         if (p == NULL) p = strchr(yy_text, '(');
9525         if (p == NULL) {
9526             p = yy_text;
9527             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9528         } else {
9529             q = strchr(p, *p == '{' ? '}' : ')');
9530             if (q != NULL) *q = NULLCHAR;
9531             p++;
9532         }
9533         GameEnds(moveType, p, GE_FILE);
9534         done = TRUE;
9535         if (cmailMsgLoaded) {
9536             ClearHighlights();
9537             flipView = WhiteOnMove(currentMove);
9538             if (moveType == GameUnfinished) flipView = !flipView;
9539             if (appData.debugMode)
9540               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9541         }
9542         break;
9543
9544       case (ChessMove) 0:       /* end of file */
9545         if (appData.debugMode)
9546           fprintf(debugFP, "Parser hit end of file\n");
9547         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9548           case MT_NONE:
9549           case MT_CHECK:
9550             break;
9551           case MT_CHECKMATE:
9552           case MT_STAINMATE:
9553             if (WhiteOnMove(currentMove)) {
9554                 GameEnds(BlackWins, "Black mates", GE_FILE);
9555             } else {
9556                 GameEnds(WhiteWins, "White mates", GE_FILE);
9557             }
9558             break;
9559           case MT_STALEMATE:
9560             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9561             break;
9562         }
9563         done = TRUE;
9564         break;
9565
9566       case MoveNumberOne:
9567         if (lastLoadGameStart == GNUChessGame) {
9568             /* GNUChessGames have numbers, but they aren't move numbers */
9569             if (appData.debugMode)
9570               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9571                       yy_text, (int) moveType);
9572             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9573         }
9574         /* else fall thru */
9575
9576       case XBoardGame:
9577       case GNUChessGame:
9578       case PGNTag:
9579         /* Reached start of next game in file */
9580         if (appData.debugMode)
9581           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9582         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9583           case MT_NONE:
9584           case MT_CHECK:
9585             break;
9586           case MT_CHECKMATE:
9587           case MT_STAINMATE:
9588             if (WhiteOnMove(currentMove)) {
9589                 GameEnds(BlackWins, "Black mates", GE_FILE);
9590             } else {
9591                 GameEnds(WhiteWins, "White mates", GE_FILE);
9592             }
9593             break;
9594           case MT_STALEMATE:
9595             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9596             break;
9597         }
9598         done = TRUE;
9599         break;
9600
9601       case PositionDiagram:     /* should not happen; ignore */
9602       case ElapsedTime:         /* ignore */
9603       case NAG:                 /* ignore */
9604         if (appData.debugMode)
9605           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9606                   yy_text, (int) moveType);
9607         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9608
9609       case IllegalMove:
9610         if (appData.testLegality) {
9611             if (appData.debugMode)
9612               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9613             sprintf(move, _("Illegal move: %d.%s%s"),
9614                     (forwardMostMove / 2) + 1,
9615                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9616             DisplayError(move, 0);
9617             done = TRUE;
9618         } else {
9619             if (appData.debugMode)
9620               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9621                       yy_text, currentMoveString);
9622             fromX = currentMoveString[0] - AAA;
9623             fromY = currentMoveString[1] - ONE;
9624             toX = currentMoveString[2] - AAA;
9625             toY = currentMoveString[3] - ONE;
9626             promoChar = currentMoveString[4];
9627         }
9628         break;
9629
9630       case AmbiguousMove:
9631         if (appData.debugMode)
9632           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9633         sprintf(move, _("Ambiguous move: %d.%s%s"),
9634                 (forwardMostMove / 2) + 1,
9635                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9636         DisplayError(move, 0);
9637         done = TRUE;
9638         break;
9639
9640       default:
9641       case ImpossibleMove:
9642         if (appData.debugMode)
9643           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9644         sprintf(move, _("Illegal move: %d.%s%s"),
9645                 (forwardMostMove / 2) + 1,
9646                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9647         DisplayError(move, 0);
9648         done = TRUE;
9649         break;
9650     }
9651
9652     if (done) {
9653         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9654             DrawPosition(FALSE, boards[currentMove]);
9655             DisplayBothClocks();
9656             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9657               DisplayComment(currentMove - 1, commentList[currentMove]);
9658         }
9659         (void) StopLoadGameTimer();
9660         gameFileFP = NULL;
9661         cmailOldMove = forwardMostMove;
9662         return FALSE;
9663     } else {
9664         /* currentMoveString is set as a side-effect of yylex */
9665         strcat(currentMoveString, "\n");
9666         strcpy(moveList[forwardMostMove], currentMoveString);
9667         
9668         thinkOutput[0] = NULLCHAR;
9669         MakeMove(fromX, fromY, toX, toY, promoChar);
9670         currentMove = forwardMostMove;
9671         return TRUE;
9672     }
9673 }
9674
9675 /* Load the nth game from the given file */
9676 int
9677 LoadGameFromFile(filename, n, title, useList)
9678      char *filename;
9679      int n;
9680      char *title;
9681      /*Boolean*/ int useList;
9682 {
9683     FILE *f;
9684     char buf[MSG_SIZ];
9685
9686     if (strcmp(filename, "-") == 0) {
9687         f = stdin;
9688         title = "stdin";
9689     } else {
9690         f = fopen(filename, "rb");
9691         if (f == NULL) {
9692           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9693             DisplayError(buf, errno);
9694             return FALSE;
9695         }
9696     }
9697     if (fseek(f, 0, 0) == -1) {
9698         /* f is not seekable; probably a pipe */
9699         useList = FALSE;
9700     }
9701     if (useList && n == 0) {
9702         int error = GameListBuild(f);
9703         if (error) {
9704             DisplayError(_("Cannot build game list"), error);
9705         } else if (!ListEmpty(&gameList) &&
9706                    ((ListGame *) gameList.tailPred)->number > 1) {
9707             GameListPopUp(f, title);
9708             return TRUE;
9709         }
9710         GameListDestroy();
9711         n = 1;
9712     }
9713     if (n == 0) n = 1;
9714     return LoadGame(f, n, title, FALSE);
9715 }
9716
9717
9718 void
9719 MakeRegisteredMove()
9720 {
9721     int fromX, fromY, toX, toY;
9722     char promoChar;
9723     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9724         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9725           case CMAIL_MOVE:
9726           case CMAIL_DRAW:
9727             if (appData.debugMode)
9728               fprintf(debugFP, "Restoring %s for game %d\n",
9729                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9730     
9731             thinkOutput[0] = NULLCHAR;
9732             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9733             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9734             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9735             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9736             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9737             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9738             MakeMove(fromX, fromY, toX, toY, promoChar);
9739             ShowMove(fromX, fromY, toX, toY);
9740               
9741             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9742               case MT_NONE:
9743               case MT_CHECK:
9744                 break;
9745                 
9746               case MT_CHECKMATE:
9747               case MT_STAINMATE:
9748                 if (WhiteOnMove(currentMove)) {
9749                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9750                 } else {
9751                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9752                 }
9753                 break;
9754                 
9755               case MT_STALEMATE:
9756                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9757                 break;
9758             }
9759
9760             break;
9761             
9762           case CMAIL_RESIGN:
9763             if (WhiteOnMove(currentMove)) {
9764                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9765             } else {
9766                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9767             }
9768             break;
9769             
9770           case CMAIL_ACCEPT:
9771             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9772             break;
9773               
9774           default:
9775             break;
9776         }
9777     }
9778
9779     return;
9780 }
9781
9782 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9783 int
9784 CmailLoadGame(f, gameNumber, title, useList)
9785      FILE *f;
9786      int gameNumber;
9787      char *title;
9788      int useList;
9789 {
9790     int retVal;
9791
9792     if (gameNumber > nCmailGames) {
9793         DisplayError(_("No more games in this message"), 0);
9794         return FALSE;
9795     }
9796     if (f == lastLoadGameFP) {
9797         int offset = gameNumber - lastLoadGameNumber;
9798         if (offset == 0) {
9799             cmailMsg[0] = NULLCHAR;
9800             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9801                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9802                 nCmailMovesRegistered--;
9803             }
9804             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9805             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9806                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9807             }
9808         } else {
9809             if (! RegisterMove()) return FALSE;
9810         }
9811     }
9812
9813     retVal = LoadGame(f, gameNumber, title, useList);
9814
9815     /* Make move registered during previous look at this game, if any */
9816     MakeRegisteredMove();
9817
9818     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9819         commentList[currentMove]
9820           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9821         DisplayComment(currentMove - 1, commentList[currentMove]);
9822     }
9823
9824     return retVal;
9825 }
9826
9827 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9828 int
9829 ReloadGame(offset)
9830      int offset;
9831 {
9832     int gameNumber = lastLoadGameNumber + offset;
9833     if (lastLoadGameFP == NULL) {
9834         DisplayError(_("No game has been loaded yet"), 0);
9835         return FALSE;
9836     }
9837     if (gameNumber <= 0) {
9838         DisplayError(_("Can't back up any further"), 0);
9839         return FALSE;
9840     }
9841     if (cmailMsgLoaded) {
9842         return CmailLoadGame(lastLoadGameFP, gameNumber,
9843                              lastLoadGameTitle, lastLoadGameUseList);
9844     } else {
9845         return LoadGame(lastLoadGameFP, gameNumber,
9846                         lastLoadGameTitle, lastLoadGameUseList);
9847     }
9848 }
9849
9850
9851
9852 /* Load the nth game from open file f */
9853 int
9854 LoadGame(f, gameNumber, title, useList)
9855      FILE *f;
9856      int gameNumber;
9857      char *title;
9858      int useList;
9859 {
9860     ChessMove cm;
9861     char buf[MSG_SIZ];
9862     int gn = gameNumber;
9863     ListGame *lg = NULL;
9864     int numPGNTags = 0;
9865     int err;
9866     GameMode oldGameMode;
9867     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9868
9869     if (appData.debugMode) 
9870         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9871
9872     if (gameMode == Training )
9873         SetTrainingModeOff();
9874
9875     oldGameMode = gameMode;
9876     if (gameMode != BeginningOfGame) {
9877       Reset(FALSE, TRUE);
9878     }
9879
9880     gameFileFP = f;
9881     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9882         fclose(lastLoadGameFP);
9883     }
9884
9885     if (useList) {
9886         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9887         
9888         if (lg) {
9889             fseek(f, lg->offset, 0);
9890             GameListHighlight(gameNumber);
9891             gn = 1;
9892         }
9893         else {
9894             DisplayError(_("Game number out of range"), 0);
9895             return FALSE;
9896         }
9897     } else {
9898         GameListDestroy();
9899         if (fseek(f, 0, 0) == -1) {
9900             if (f == lastLoadGameFP ?
9901                 gameNumber == lastLoadGameNumber + 1 :
9902                 gameNumber == 1) {
9903                 gn = 1;
9904             } else {
9905                 DisplayError(_("Can't seek on game file"), 0);
9906                 return FALSE;
9907             }
9908         }
9909     }
9910     lastLoadGameFP = f;
9911     lastLoadGameNumber = gameNumber;
9912     strcpy(lastLoadGameTitle, title);
9913     lastLoadGameUseList = useList;
9914
9915     yynewfile(f);
9916
9917     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9918       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9919                 lg->gameInfo.black);
9920             DisplayTitle(buf);
9921     } else if (*title != NULLCHAR) {
9922         if (gameNumber > 1) {
9923             sprintf(buf, "%s %d", title, gameNumber);
9924             DisplayTitle(buf);
9925         } else {
9926             DisplayTitle(title);
9927         }
9928     }
9929
9930     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9931         gameMode = PlayFromGameFile;
9932         ModeHighlight();
9933     }
9934
9935     currentMove = forwardMostMove = backwardMostMove = 0;
9936     CopyBoard(boards[0], initialPosition);
9937     StopClocks();
9938
9939     /*
9940      * Skip the first gn-1 games in the file.
9941      * Also skip over anything that precedes an identifiable 
9942      * start of game marker, to avoid being confused by 
9943      * garbage at the start of the file.  Currently 
9944      * recognized start of game markers are the move number "1",
9945      * the pattern "gnuchess .* game", the pattern
9946      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9947      * A game that starts with one of the latter two patterns
9948      * will also have a move number 1, possibly
9949      * following a position diagram.
9950      * 5-4-02: Let's try being more lenient and allowing a game to
9951      * start with an unnumbered move.  Does that break anything?
9952      */
9953     cm = lastLoadGameStart = (ChessMove) 0;
9954     while (gn > 0) {
9955         yyboardindex = forwardMostMove;
9956         cm = (ChessMove) yylex();
9957         switch (cm) {
9958           case (ChessMove) 0:
9959             if (cmailMsgLoaded) {
9960                 nCmailGames = CMAIL_MAX_GAMES - gn;
9961             } else {
9962                 Reset(TRUE, TRUE);
9963                 DisplayError(_("Game not found in file"), 0);
9964             }
9965             return FALSE;
9966
9967           case GNUChessGame:
9968           case XBoardGame:
9969             gn--;
9970             lastLoadGameStart = cm;
9971             break;
9972             
9973           case MoveNumberOne:
9974             switch (lastLoadGameStart) {
9975               case GNUChessGame:
9976               case XBoardGame:
9977               case PGNTag:
9978                 break;
9979               case MoveNumberOne:
9980               case (ChessMove) 0:
9981                 gn--;           /* count this game */
9982                 lastLoadGameStart = cm;
9983                 break;
9984               default:
9985                 /* impossible */
9986                 break;
9987             }
9988             break;
9989
9990           case PGNTag:
9991             switch (lastLoadGameStart) {
9992               case GNUChessGame:
9993               case PGNTag:
9994               case MoveNumberOne:
9995               case (ChessMove) 0:
9996                 gn--;           /* count this game */
9997                 lastLoadGameStart = cm;
9998                 break;
9999               case XBoardGame:
10000                 lastLoadGameStart = cm; /* game counted already */
10001                 break;
10002               default:
10003                 /* impossible */
10004                 break;
10005             }
10006             if (gn > 0) {
10007                 do {
10008                     yyboardindex = forwardMostMove;
10009                     cm = (ChessMove) yylex();
10010                 } while (cm == PGNTag || cm == Comment);
10011             }
10012             break;
10013
10014           case WhiteWins:
10015           case BlackWins:
10016           case GameIsDrawn:
10017             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10018                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10019                     != CMAIL_OLD_RESULT) {
10020                     nCmailResults ++ ;
10021                     cmailResult[  CMAIL_MAX_GAMES
10022                                 - gn - 1] = CMAIL_OLD_RESULT;
10023                 }
10024             }
10025             break;
10026
10027           case NormalMove:
10028             /* Only a NormalMove can be at the start of a game
10029              * without a position diagram. */
10030             if (lastLoadGameStart == (ChessMove) 0) {
10031               gn--;
10032               lastLoadGameStart = MoveNumberOne;
10033             }
10034             break;
10035
10036           default:
10037             break;
10038         }
10039     }
10040     
10041     if (appData.debugMode)
10042       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10043
10044     if (cm == XBoardGame) {
10045         /* Skip any header junk before position diagram and/or move 1 */
10046         for (;;) {
10047             yyboardindex = forwardMostMove;
10048             cm = (ChessMove) yylex();
10049
10050             if (cm == (ChessMove) 0 ||
10051                 cm == GNUChessGame || cm == XBoardGame) {
10052                 /* Empty game; pretend end-of-file and handle later */
10053                 cm = (ChessMove) 0;
10054                 break;
10055             }
10056
10057             if (cm == MoveNumberOne || cm == PositionDiagram ||
10058                 cm == PGNTag || cm == Comment)
10059               break;
10060         }
10061     } else if (cm == GNUChessGame) {
10062         if (gameInfo.event != NULL) {
10063             free(gameInfo.event);
10064         }
10065         gameInfo.event = StrSave(yy_text);
10066     }   
10067
10068     startedFromSetupPosition = FALSE;
10069     while (cm == PGNTag) {
10070         if (appData.debugMode) 
10071           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10072         err = ParsePGNTag(yy_text, &gameInfo);
10073         if (!err) numPGNTags++;
10074
10075         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10076         if(gameInfo.variant != oldVariant) {
10077             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10078             InitPosition(TRUE);
10079             oldVariant = gameInfo.variant;
10080             if (appData.debugMode) 
10081               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10082         }
10083
10084
10085         if (gameInfo.fen != NULL) {
10086           Board initial_position;
10087           startedFromSetupPosition = TRUE;
10088           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10089             Reset(TRUE, TRUE);
10090             DisplayError(_("Bad FEN position in file"), 0);
10091             return FALSE;
10092           }
10093           CopyBoard(boards[0], initial_position);
10094           if (blackPlaysFirst) {
10095             currentMove = forwardMostMove = backwardMostMove = 1;
10096             CopyBoard(boards[1], initial_position);
10097             strcpy(moveList[0], "");
10098             strcpy(parseList[0], "");
10099             timeRemaining[0][1] = whiteTimeRemaining;
10100             timeRemaining[1][1] = blackTimeRemaining;
10101             if (commentList[0] != NULL) {
10102               commentList[1] = commentList[0];
10103               commentList[0] = NULL;
10104             }
10105           } else {
10106             currentMove = forwardMostMove = backwardMostMove = 0;
10107           }
10108           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10109           {   int i;
10110               initialRulePlies = FENrulePlies;
10111               for( i=0; i< nrCastlingRights; i++ )
10112                   initialRights[i] = initial_position[CASTLING][i];
10113           }
10114           yyboardindex = forwardMostMove;
10115           free(gameInfo.fen);
10116           gameInfo.fen = NULL;
10117         }
10118
10119         yyboardindex = forwardMostMove;
10120         cm = (ChessMove) yylex();
10121
10122         /* Handle comments interspersed among the tags */
10123         while (cm == Comment) {
10124             char *p;
10125             if (appData.debugMode) 
10126               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10127             p = yy_text;
10128             AppendComment(currentMove, p, FALSE);
10129             yyboardindex = forwardMostMove;
10130             cm = (ChessMove) yylex();
10131         }
10132     }
10133
10134     /* don't rely on existence of Event tag since if game was
10135      * pasted from clipboard the Event tag may not exist
10136      */
10137     if (numPGNTags > 0){
10138         char *tags;
10139         if (gameInfo.variant == VariantNormal) {
10140           gameInfo.variant = StringToVariant(gameInfo.event);
10141         }
10142         if (!matchMode) {
10143           if( appData.autoDisplayTags ) {
10144             tags = PGNTags(&gameInfo);
10145             TagsPopUp(tags, CmailMsg());
10146             free(tags);
10147           }
10148         }
10149     } else {
10150         /* Make something up, but don't display it now */
10151         SetGameInfo();
10152         TagsPopDown();
10153     }
10154
10155     if (cm == PositionDiagram) {
10156         int i, j;
10157         char *p;
10158         Board initial_position;
10159
10160         if (appData.debugMode)
10161           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10162
10163         if (!startedFromSetupPosition) {
10164             p = yy_text;
10165             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10166               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10167                 switch (*p) {
10168                   case '[':
10169                   case '-':
10170                   case ' ':
10171                   case '\t':
10172                   case '\n':
10173                   case '\r':
10174                     break;
10175                   default:
10176                     initial_position[i][j++] = CharToPiece(*p);
10177                     break;
10178                 }
10179             while (*p == ' ' || *p == '\t' ||
10180                    *p == '\n' || *p == '\r') p++;
10181         
10182             if (strncmp(p, "black", strlen("black"))==0)
10183               blackPlaysFirst = TRUE;
10184             else
10185               blackPlaysFirst = FALSE;
10186             startedFromSetupPosition = TRUE;
10187         
10188             CopyBoard(boards[0], initial_position);
10189             if (blackPlaysFirst) {
10190                 currentMove = forwardMostMove = backwardMostMove = 1;
10191                 CopyBoard(boards[1], initial_position);
10192                 strcpy(moveList[0], "");
10193                 strcpy(parseList[0], "");
10194                 timeRemaining[0][1] = whiteTimeRemaining;
10195                 timeRemaining[1][1] = blackTimeRemaining;
10196                 if (commentList[0] != NULL) {
10197                     commentList[1] = commentList[0];
10198                     commentList[0] = NULL;
10199                 }
10200             } else {
10201                 currentMove = forwardMostMove = backwardMostMove = 0;
10202             }
10203         }
10204         yyboardindex = forwardMostMove;
10205         cm = (ChessMove) yylex();
10206     }
10207
10208     if (first.pr == NoProc) {
10209         StartChessProgram(&first);
10210     }
10211     InitChessProgram(&first, FALSE);
10212     SendToProgram("force\n", &first);
10213     if (startedFromSetupPosition) {
10214         SendBoard(&first, forwardMostMove);
10215     if (appData.debugMode) {
10216         fprintf(debugFP, "Load Game\n");
10217     }
10218         DisplayBothClocks();
10219     }      
10220
10221     /* [HGM] server: flag to write setup moves in broadcast file as one */
10222     loadFlag = appData.suppressLoadMoves;
10223
10224     while (cm == Comment) {
10225         char *p;
10226         if (appData.debugMode) 
10227           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10228         p = yy_text;
10229         AppendComment(currentMove, p, FALSE);
10230         yyboardindex = forwardMostMove;
10231         cm = (ChessMove) yylex();
10232     }
10233
10234     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10235         cm == WhiteWins || cm == BlackWins ||
10236         cm == GameIsDrawn || cm == GameUnfinished) {
10237         DisplayMessage("", _("No moves in game"));
10238         if (cmailMsgLoaded) {
10239             if (appData.debugMode)
10240               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10241             ClearHighlights();
10242             flipView = FALSE;
10243         }
10244         DrawPosition(FALSE, boards[currentMove]);
10245         DisplayBothClocks();
10246         gameMode = EditGame;
10247         ModeHighlight();
10248         gameFileFP = NULL;
10249         cmailOldMove = 0;
10250         return TRUE;
10251     }
10252
10253     // [HGM] PV info: routine tests if comment empty
10254     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10255         DisplayComment(currentMove - 1, commentList[currentMove]);
10256     }
10257     if (!matchMode && appData.timeDelay != 0) 
10258       DrawPosition(FALSE, boards[currentMove]);
10259
10260     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10261       programStats.ok_to_send = 1;
10262     }
10263
10264     /* if the first token after the PGN tags is a move
10265      * and not move number 1, retrieve it from the parser 
10266      */
10267     if (cm != MoveNumberOne)
10268         LoadGameOneMove(cm);
10269
10270     /* load the remaining moves from the file */
10271     while (LoadGameOneMove((ChessMove)0)) {
10272       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10273       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10274     }
10275
10276     /* rewind to the start of the game */
10277     currentMove = backwardMostMove;
10278
10279     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10280
10281     if (oldGameMode == AnalyzeFile ||
10282         oldGameMode == AnalyzeMode) {
10283       AnalyzeFileEvent();
10284     }
10285
10286     if (matchMode || appData.timeDelay == 0) {
10287       ToEndEvent();
10288       gameMode = EditGame;
10289       ModeHighlight();
10290     } else if (appData.timeDelay > 0) {
10291       AutoPlayGameLoop();
10292     }
10293
10294     if (appData.debugMode) 
10295         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10296
10297     loadFlag = 0; /* [HGM] true game starts */
10298     return TRUE;
10299 }
10300
10301 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10302 int
10303 ReloadPosition(offset)
10304      int offset;
10305 {
10306     int positionNumber = lastLoadPositionNumber + offset;
10307     if (lastLoadPositionFP == NULL) {
10308         DisplayError(_("No position has been loaded yet"), 0);
10309         return FALSE;
10310     }
10311     if (positionNumber <= 0) {
10312         DisplayError(_("Can't back up any further"), 0);
10313         return FALSE;
10314     }
10315     return LoadPosition(lastLoadPositionFP, positionNumber,
10316                         lastLoadPositionTitle);
10317 }
10318
10319 /* Load the nth position from the given file */
10320 int
10321 LoadPositionFromFile(filename, n, title)
10322      char *filename;
10323      int n;
10324      char *title;
10325 {
10326     FILE *f;
10327     char buf[MSG_SIZ];
10328
10329     if (strcmp(filename, "-") == 0) {
10330         return LoadPosition(stdin, n, "stdin");
10331     } else {
10332         f = fopen(filename, "rb");
10333         if (f == NULL) {
10334             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10335             DisplayError(buf, errno);
10336             return FALSE;
10337         } else {
10338             return LoadPosition(f, n, title);
10339         }
10340     }
10341 }
10342
10343 /* Load the nth position from the given open file, and close it */
10344 int
10345 LoadPosition(f, positionNumber, title)
10346      FILE *f;
10347      int positionNumber;
10348      char *title;
10349 {
10350     char *p, line[MSG_SIZ];
10351     Board initial_position;
10352     int i, j, fenMode, pn;
10353     
10354     if (gameMode == Training )
10355         SetTrainingModeOff();
10356
10357     if (gameMode != BeginningOfGame) {
10358         Reset(FALSE, TRUE);
10359     }
10360     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10361         fclose(lastLoadPositionFP);
10362     }
10363     if (positionNumber == 0) positionNumber = 1;
10364     lastLoadPositionFP = f;
10365     lastLoadPositionNumber = positionNumber;
10366     strcpy(lastLoadPositionTitle, title);
10367     if (first.pr == NoProc) {
10368       StartChessProgram(&first);
10369       InitChessProgram(&first, FALSE);
10370     }    
10371     pn = positionNumber;
10372     if (positionNumber < 0) {
10373         /* Negative position number means to seek to that byte offset */
10374         if (fseek(f, -positionNumber, 0) == -1) {
10375             DisplayError(_("Can't seek on position file"), 0);
10376             return FALSE;
10377         };
10378         pn = 1;
10379     } else {
10380         if (fseek(f, 0, 0) == -1) {
10381             if (f == lastLoadPositionFP ?
10382                 positionNumber == lastLoadPositionNumber + 1 :
10383                 positionNumber == 1) {
10384                 pn = 1;
10385             } else {
10386                 DisplayError(_("Can't seek on position file"), 0);
10387                 return FALSE;
10388             }
10389         }
10390     }
10391     /* See if this file is FEN or old-style xboard */
10392     if (fgets(line, MSG_SIZ, f) == NULL) {
10393         DisplayError(_("Position not found in file"), 0);
10394         return FALSE;
10395     }
10396     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10397     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10398
10399     if (pn >= 2) {
10400         if (fenMode || line[0] == '#') pn--;
10401         while (pn > 0) {
10402             /* skip positions before number pn */
10403             if (fgets(line, MSG_SIZ, f) == NULL) {
10404                 Reset(TRUE, TRUE);
10405                 DisplayError(_("Position not found in file"), 0);
10406                 return FALSE;
10407             }
10408             if (fenMode || line[0] == '#') pn--;
10409         }
10410     }
10411
10412     if (fenMode) {
10413         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10414             DisplayError(_("Bad FEN position in file"), 0);
10415             return FALSE;
10416         }
10417     } else {
10418         (void) fgets(line, MSG_SIZ, f);
10419         (void) fgets(line, MSG_SIZ, f);
10420     
10421         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10422             (void) fgets(line, MSG_SIZ, f);
10423             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10424                 if (*p == ' ')
10425                   continue;
10426                 initial_position[i][j++] = CharToPiece(*p);
10427             }
10428         }
10429     
10430         blackPlaysFirst = FALSE;
10431         if (!feof(f)) {
10432             (void) fgets(line, MSG_SIZ, f);
10433             if (strncmp(line, "black", strlen("black"))==0)
10434               blackPlaysFirst = TRUE;
10435         }
10436     }
10437     startedFromSetupPosition = TRUE;
10438     
10439     SendToProgram("force\n", &first);
10440     CopyBoard(boards[0], initial_position);
10441     if (blackPlaysFirst) {
10442         currentMove = forwardMostMove = backwardMostMove = 1;
10443         strcpy(moveList[0], "");
10444         strcpy(parseList[0], "");
10445         CopyBoard(boards[1], initial_position);
10446         DisplayMessage("", _("Black to play"));
10447     } else {
10448         currentMove = forwardMostMove = backwardMostMove = 0;
10449         DisplayMessage("", _("White to play"));
10450     }
10451     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10452     SendBoard(&first, forwardMostMove);
10453     if (appData.debugMode) {
10454 int i, j;
10455   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10456   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10457         fprintf(debugFP, "Load Position\n");
10458     }
10459
10460     if (positionNumber > 1) {
10461         sprintf(line, "%s %d", title, positionNumber);
10462         DisplayTitle(line);
10463     } else {
10464         DisplayTitle(title);
10465     }
10466     gameMode = EditGame;
10467     ModeHighlight();
10468     ResetClocks();
10469     timeRemaining[0][1] = whiteTimeRemaining;
10470     timeRemaining[1][1] = blackTimeRemaining;
10471     DrawPosition(FALSE, boards[currentMove]);
10472    
10473     return TRUE;
10474 }
10475
10476
10477 void
10478 CopyPlayerNameIntoFileName(dest, src)
10479      char **dest, *src;
10480 {
10481     while (*src != NULLCHAR && *src != ',') {
10482         if (*src == ' ') {
10483             *(*dest)++ = '_';
10484             src++;
10485         } else {
10486             *(*dest)++ = *src++;
10487         }
10488     }
10489 }
10490
10491 char *DefaultFileName(ext)
10492      char *ext;
10493 {
10494     static char def[MSG_SIZ];
10495     char *p;
10496
10497     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10498         p = def;
10499         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10500         *p++ = '-';
10501         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10502         *p++ = '.';
10503         strcpy(p, ext);
10504     } else {
10505         def[0] = NULLCHAR;
10506     }
10507     return def;
10508 }
10509
10510 /* Save the current game to the given file */
10511 int
10512 SaveGameToFile(filename, append)
10513      char *filename;
10514      int append;
10515 {
10516     FILE *f;
10517     char buf[MSG_SIZ];
10518
10519     if (strcmp(filename, "-") == 0) {
10520         return SaveGame(stdout, 0, NULL);
10521     } else {
10522         f = fopen(filename, append ? "a" : "w");
10523         if (f == NULL) {
10524             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10525             DisplayError(buf, errno);
10526             return FALSE;
10527         } else {
10528             return SaveGame(f, 0, NULL);
10529         }
10530     }
10531 }
10532
10533 char *
10534 SavePart(str)
10535      char *str;
10536 {
10537     static char buf[MSG_SIZ];
10538     char *p;
10539     
10540     p = strchr(str, ' ');
10541     if (p == NULL) return str;
10542     strncpy(buf, str, p - str);
10543     buf[p - str] = NULLCHAR;
10544     return buf;
10545 }
10546
10547 #define PGN_MAX_LINE 75
10548
10549 #define PGN_SIDE_WHITE  0
10550 #define PGN_SIDE_BLACK  1
10551
10552 /* [AS] */
10553 static int FindFirstMoveOutOfBook( int side )
10554 {
10555     int result = -1;
10556
10557     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10558         int index = backwardMostMove;
10559         int has_book_hit = 0;
10560
10561         if( (index % 2) != side ) {
10562             index++;
10563         }
10564
10565         while( index < forwardMostMove ) {
10566             /* Check to see if engine is in book */
10567             int depth = pvInfoList[index].depth;
10568             int score = pvInfoList[index].score;
10569             int in_book = 0;
10570
10571             if( depth <= 2 ) {
10572                 in_book = 1;
10573             }
10574             else if( score == 0 && depth == 63 ) {
10575                 in_book = 1; /* Zappa */
10576             }
10577             else if( score == 2 && depth == 99 ) {
10578                 in_book = 1; /* Abrok */
10579             }
10580
10581             has_book_hit += in_book;
10582
10583             if( ! in_book ) {
10584                 result = index;
10585
10586                 break;
10587             }
10588
10589             index += 2;
10590         }
10591     }
10592
10593     return result;
10594 }
10595
10596 /* [AS] */
10597 void GetOutOfBookInfo( char * buf )
10598 {
10599     int oob[2];
10600     int i;
10601     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10602
10603     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10604     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10605
10606     *buf = '\0';
10607
10608     if( oob[0] >= 0 || oob[1] >= 0 ) {
10609         for( i=0; i<2; i++ ) {
10610             int idx = oob[i];
10611
10612             if( idx >= 0 ) {
10613                 if( i > 0 && oob[0] >= 0 ) {
10614                     strcat( buf, "   " );
10615                 }
10616
10617                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10618                 sprintf( buf+strlen(buf), "%s%.2f", 
10619                     pvInfoList[idx].score >= 0 ? "+" : "",
10620                     pvInfoList[idx].score / 100.0 );
10621             }
10622         }
10623     }
10624 }
10625
10626 /* Save game in PGN style and close the file */
10627 int
10628 SaveGamePGN(f)
10629      FILE *f;
10630 {
10631     int i, offset, linelen, newblock;
10632     time_t tm;
10633 //    char *movetext;
10634     char numtext[32];
10635     int movelen, numlen, blank;
10636     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10637
10638     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10639     
10640     tm = time((time_t *) NULL);
10641     
10642     PrintPGNTags(f, &gameInfo);
10643     
10644     if (backwardMostMove > 0 || startedFromSetupPosition) {
10645         char *fen = PositionToFEN(backwardMostMove, NULL);
10646         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10647         fprintf(f, "\n{--------------\n");
10648         PrintPosition(f, backwardMostMove);
10649         fprintf(f, "--------------}\n");
10650         free(fen);
10651     }
10652     else {
10653         /* [AS] Out of book annotation */
10654         if( appData.saveOutOfBookInfo ) {
10655             char buf[64];
10656
10657             GetOutOfBookInfo( buf );
10658
10659             if( buf[0] != '\0' ) {
10660                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10661             }
10662         }
10663
10664         fprintf(f, "\n");
10665     }
10666
10667     i = backwardMostMove;
10668     linelen = 0;
10669     newblock = TRUE;
10670
10671     while (i < forwardMostMove) {
10672         /* Print comments preceding this move */
10673         if (commentList[i] != NULL) {
10674             if (linelen > 0) fprintf(f, "\n");
10675             fprintf(f, "%s", commentList[i]);
10676             linelen = 0;
10677             newblock = TRUE;
10678         }
10679
10680         /* Format move number */
10681         if ((i % 2) == 0) {
10682             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10683         } else {
10684             if (newblock) {
10685                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10686             } else {
10687                 numtext[0] = NULLCHAR;
10688             }
10689         }
10690         numlen = strlen(numtext);
10691         newblock = FALSE;
10692
10693         /* Print move number */
10694         blank = linelen > 0 && numlen > 0;
10695         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10696             fprintf(f, "\n");
10697             linelen = 0;
10698             blank = 0;
10699         }
10700         if (blank) {
10701             fprintf(f, " ");
10702             linelen++;
10703         }
10704         fprintf(f, "%s", numtext);
10705         linelen += numlen;
10706
10707         /* Get move */
10708         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10709         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10710
10711         /* Print move */
10712         blank = linelen > 0 && movelen > 0;
10713         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10714             fprintf(f, "\n");
10715             linelen = 0;
10716             blank = 0;
10717         }
10718         if (blank) {
10719             fprintf(f, " ");
10720             linelen++;
10721         }
10722         fprintf(f, "%s", move_buffer);
10723         linelen += movelen;
10724
10725         /* [AS] Add PV info if present */
10726         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10727             /* [HGM] add time */
10728             char buf[MSG_SIZ]; int seconds;
10729
10730             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10731
10732             if( seconds <= 0) buf[0] = 0; else
10733             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10734                 seconds = (seconds + 4)/10; // round to full seconds
10735                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10736                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10737             }
10738
10739             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10740                 pvInfoList[i].score >= 0 ? "+" : "",
10741                 pvInfoList[i].score / 100.0,
10742                 pvInfoList[i].depth,
10743                 buf );
10744
10745             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10746
10747             /* Print score/depth */
10748             blank = linelen > 0 && movelen > 0;
10749             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10750                 fprintf(f, "\n");
10751                 linelen = 0;
10752                 blank = 0;
10753             }
10754             if (blank) {
10755                 fprintf(f, " ");
10756                 linelen++;
10757             }
10758             fprintf(f, "%s", move_buffer);
10759             linelen += movelen;
10760         }
10761
10762         i++;
10763     }
10764     
10765     /* Start a new line */
10766     if (linelen > 0) fprintf(f, "\n");
10767
10768     /* Print comments after last move */
10769     if (commentList[i] != NULL) {
10770         fprintf(f, "%s\n", commentList[i]);
10771     }
10772
10773     /* Print result */
10774     if (gameInfo.resultDetails != NULL &&
10775         gameInfo.resultDetails[0] != NULLCHAR) {
10776         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10777                 PGNResult(gameInfo.result));
10778     } else {
10779         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10780     }
10781
10782     fclose(f);
10783     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10784     return TRUE;
10785 }
10786
10787 /* Save game in old style and close the file */
10788 int
10789 SaveGameOldStyle(f)
10790      FILE *f;
10791 {
10792     int i, offset;
10793     time_t tm;
10794     
10795     tm = time((time_t *) NULL);
10796     
10797     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10798     PrintOpponents(f);
10799     
10800     if (backwardMostMove > 0 || startedFromSetupPosition) {
10801         fprintf(f, "\n[--------------\n");
10802         PrintPosition(f, backwardMostMove);
10803         fprintf(f, "--------------]\n");
10804     } else {
10805         fprintf(f, "\n");
10806     }
10807
10808     i = backwardMostMove;
10809     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10810
10811     while (i < forwardMostMove) {
10812         if (commentList[i] != NULL) {
10813             fprintf(f, "[%s]\n", commentList[i]);
10814         }
10815
10816         if ((i % 2) == 1) {
10817             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10818             i++;
10819         } else {
10820             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10821             i++;
10822             if (commentList[i] != NULL) {
10823                 fprintf(f, "\n");
10824                 continue;
10825             }
10826             if (i >= forwardMostMove) {
10827                 fprintf(f, "\n");
10828                 break;
10829             }
10830             fprintf(f, "%s\n", parseList[i]);
10831             i++;
10832         }
10833     }
10834     
10835     if (commentList[i] != NULL) {
10836         fprintf(f, "[%s]\n", commentList[i]);
10837     }
10838
10839     /* This isn't really the old style, but it's close enough */
10840     if (gameInfo.resultDetails != NULL &&
10841         gameInfo.resultDetails[0] != NULLCHAR) {
10842         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10843                 gameInfo.resultDetails);
10844     } else {
10845         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10846     }
10847
10848     fclose(f);
10849     return TRUE;
10850 }
10851
10852 /* Save the current game to open file f and close the file */
10853 int
10854 SaveGame(f, dummy, dummy2)
10855      FILE *f;
10856      int dummy;
10857      char *dummy2;
10858 {
10859     if (gameMode == EditPosition) EditPositionDone(TRUE);
10860     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10861     if (appData.oldSaveStyle)
10862       return SaveGameOldStyle(f);
10863     else
10864       return SaveGamePGN(f);
10865 }
10866
10867 /* Save the current position to the given file */
10868 int
10869 SavePositionToFile(filename)
10870      char *filename;
10871 {
10872     FILE *f;
10873     char buf[MSG_SIZ];
10874
10875     if (strcmp(filename, "-") == 0) {
10876         return SavePosition(stdout, 0, NULL);
10877     } else {
10878         f = fopen(filename, "a");
10879         if (f == NULL) {
10880             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10881             DisplayError(buf, errno);
10882             return FALSE;
10883         } else {
10884             SavePosition(f, 0, NULL);
10885             return TRUE;
10886         }
10887     }
10888 }
10889
10890 /* Save the current position to the given open file and close the file */
10891 int
10892 SavePosition(f, dummy, dummy2)
10893      FILE *f;
10894      int dummy;
10895      char *dummy2;
10896 {
10897     time_t tm;
10898     char *fen;
10899     
10900     if (gameMode == EditPosition) EditPositionDone(TRUE);
10901     if (appData.oldSaveStyle) {
10902         tm = time((time_t *) NULL);
10903     
10904         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10905         PrintOpponents(f);
10906         fprintf(f, "[--------------\n");
10907         PrintPosition(f, currentMove);
10908         fprintf(f, "--------------]\n");
10909     } else {
10910         fen = PositionToFEN(currentMove, NULL);
10911         fprintf(f, "%s\n", fen);
10912         free(fen);
10913     }
10914     fclose(f);
10915     return TRUE;
10916 }
10917
10918 void
10919 ReloadCmailMsgEvent(unregister)
10920      int unregister;
10921 {
10922 #if !WIN32
10923     static char *inFilename = NULL;
10924     static char *outFilename;
10925     int i;
10926     struct stat inbuf, outbuf;
10927     int status;
10928     
10929     /* Any registered moves are unregistered if unregister is set, */
10930     /* i.e. invoked by the signal handler */
10931     if (unregister) {
10932         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10933             cmailMoveRegistered[i] = FALSE;
10934             if (cmailCommentList[i] != NULL) {
10935                 free(cmailCommentList[i]);
10936                 cmailCommentList[i] = NULL;
10937             }
10938         }
10939         nCmailMovesRegistered = 0;
10940     }
10941
10942     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10943         cmailResult[i] = CMAIL_NOT_RESULT;
10944     }
10945     nCmailResults = 0;
10946
10947     if (inFilename == NULL) {
10948         /* Because the filenames are static they only get malloced once  */
10949         /* and they never get freed                                      */
10950         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10951         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10952
10953         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10954         sprintf(outFilename, "%s.out", appData.cmailGameName);
10955     }
10956     
10957     status = stat(outFilename, &outbuf);
10958     if (status < 0) {
10959         cmailMailedMove = FALSE;
10960     } else {
10961         status = stat(inFilename, &inbuf);
10962         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10963     }
10964     
10965     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10966        counts the games, notes how each one terminated, etc.
10967        
10968        It would be nice to remove this kludge and instead gather all
10969        the information while building the game list.  (And to keep it
10970        in the game list nodes instead of having a bunch of fixed-size
10971        parallel arrays.)  Note this will require getting each game's
10972        termination from the PGN tags, as the game list builder does
10973        not process the game moves.  --mann
10974        */
10975     cmailMsgLoaded = TRUE;
10976     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10977     
10978     /* Load first game in the file or popup game menu */
10979     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10980
10981 #endif /* !WIN32 */
10982     return;
10983 }
10984
10985 int
10986 RegisterMove()
10987 {
10988     FILE *f;
10989     char string[MSG_SIZ];
10990
10991     if (   cmailMailedMove
10992         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10993         return TRUE;            /* Allow free viewing  */
10994     }
10995
10996     /* Unregister move to ensure that we don't leave RegisterMove        */
10997     /* with the move registered when the conditions for registering no   */
10998     /* longer hold                                                       */
10999     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11000         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11001         nCmailMovesRegistered --;
11002
11003         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11004           {
11005               free(cmailCommentList[lastLoadGameNumber - 1]);
11006               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11007           }
11008     }
11009
11010     if (cmailOldMove == -1) {
11011         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11012         return FALSE;
11013     }
11014
11015     if (currentMove > cmailOldMove + 1) {
11016         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11017         return FALSE;
11018     }
11019
11020     if (currentMove < cmailOldMove) {
11021         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11022         return FALSE;
11023     }
11024
11025     if (forwardMostMove > currentMove) {
11026         /* Silently truncate extra moves */
11027         TruncateGame();
11028     }
11029
11030     if (   (currentMove == cmailOldMove + 1)
11031         || (   (currentMove == cmailOldMove)
11032             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11033                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11034         if (gameInfo.result != GameUnfinished) {
11035             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11036         }
11037
11038         if (commentList[currentMove] != NULL) {
11039             cmailCommentList[lastLoadGameNumber - 1]
11040               = StrSave(commentList[currentMove]);
11041         }
11042         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11043
11044         if (appData.debugMode)
11045           fprintf(debugFP, "Saving %s for game %d\n",
11046                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11047
11048         sprintf(string,
11049                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11050         
11051         f = fopen(string, "w");
11052         if (appData.oldSaveStyle) {
11053             SaveGameOldStyle(f); /* also closes the file */
11054             
11055             sprintf(string, "%s.pos.out", appData.cmailGameName);
11056             f = fopen(string, "w");
11057             SavePosition(f, 0, NULL); /* also closes the file */
11058         } else {
11059             fprintf(f, "{--------------\n");
11060             PrintPosition(f, currentMove);
11061             fprintf(f, "--------------}\n\n");
11062             
11063             SaveGame(f, 0, NULL); /* also closes the file*/
11064         }
11065         
11066         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11067         nCmailMovesRegistered ++;
11068     } else if (nCmailGames == 1) {
11069         DisplayError(_("You have not made a move yet"), 0);
11070         return FALSE;
11071     }
11072
11073     return TRUE;
11074 }
11075
11076 void
11077 MailMoveEvent()
11078 {
11079 #if !WIN32
11080     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11081     FILE *commandOutput;
11082     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11083     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11084     int nBuffers;
11085     int i;
11086     int archived;
11087     char *arcDir;
11088
11089     if (! cmailMsgLoaded) {
11090         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11091         return;
11092     }
11093
11094     if (nCmailGames == nCmailResults) {
11095         DisplayError(_("No unfinished games"), 0);
11096         return;
11097     }
11098
11099 #if CMAIL_PROHIBIT_REMAIL
11100     if (cmailMailedMove) {
11101         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);
11102         DisplayError(msg, 0);
11103         return;
11104     }
11105 #endif
11106
11107     if (! (cmailMailedMove || RegisterMove())) return;
11108     
11109     if (   cmailMailedMove
11110         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11111         sprintf(string, partCommandString,
11112                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11113         commandOutput = popen(string, "r");
11114
11115         if (commandOutput == NULL) {
11116             DisplayError(_("Failed to invoke cmail"), 0);
11117         } else {
11118             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11119                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11120             }
11121             if (nBuffers > 1) {
11122                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11123                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11124                 nBytes = MSG_SIZ - 1;
11125             } else {
11126                 (void) memcpy(msg, buffer, nBytes);
11127             }
11128             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11129
11130             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11131                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11132
11133                 archived = TRUE;
11134                 for (i = 0; i < nCmailGames; i ++) {
11135                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11136                         archived = FALSE;
11137                     }
11138                 }
11139                 if (   archived
11140                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11141                         != NULL)) {
11142                     sprintf(buffer, "%s/%s.%s.archive",
11143                             arcDir,
11144                             appData.cmailGameName,
11145                             gameInfo.date);
11146                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11147                     cmailMsgLoaded = FALSE;
11148                 }
11149             }
11150
11151             DisplayInformation(msg);
11152             pclose(commandOutput);
11153         }
11154     } else {
11155         if ((*cmailMsg) != '\0') {
11156             DisplayInformation(cmailMsg);
11157         }
11158     }
11159
11160     return;
11161 #endif /* !WIN32 */
11162 }
11163
11164 char *
11165 CmailMsg()
11166 {
11167 #if WIN32
11168     return NULL;
11169 #else
11170     int  prependComma = 0;
11171     char number[5];
11172     char string[MSG_SIZ];       /* Space for game-list */
11173     int  i;
11174     
11175     if (!cmailMsgLoaded) return "";
11176
11177     if (cmailMailedMove) {
11178         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11179     } else {
11180         /* Create a list of games left */
11181         sprintf(string, "[");
11182         for (i = 0; i < nCmailGames; i ++) {
11183             if (! (   cmailMoveRegistered[i]
11184                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11185                 if (prependComma) {
11186                     sprintf(number, ",%d", i + 1);
11187                 } else {
11188                     sprintf(number, "%d", i + 1);
11189                     prependComma = 1;
11190                 }
11191                 
11192                 strcat(string, number);
11193             }
11194         }
11195         strcat(string, "]");
11196
11197         if (nCmailMovesRegistered + nCmailResults == 0) {
11198             switch (nCmailGames) {
11199               case 1:
11200                 sprintf(cmailMsg,
11201                         _("Still need to make move for game\n"));
11202                 break;
11203                 
11204               case 2:
11205                 sprintf(cmailMsg,
11206                         _("Still need to make moves for both games\n"));
11207                 break;
11208                 
11209               default:
11210                 sprintf(cmailMsg,
11211                         _("Still need to make moves for all %d games\n"),
11212                         nCmailGames);
11213                 break;
11214             }
11215         } else {
11216             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11217               case 1:
11218                 sprintf(cmailMsg,
11219                         _("Still need to make a move for game %s\n"),
11220                         string);
11221                 break;
11222                 
11223               case 0:
11224                 if (nCmailResults == nCmailGames) {
11225                     sprintf(cmailMsg, _("No unfinished games\n"));
11226                 } else {
11227                     sprintf(cmailMsg, _("Ready to send mail\n"));
11228                 }
11229                 break;
11230                 
11231               default:
11232                 sprintf(cmailMsg,
11233                         _("Still need to make moves for games %s\n"),
11234                         string);
11235             }
11236         }
11237     }
11238     return cmailMsg;
11239 #endif /* WIN32 */
11240 }
11241
11242 void
11243 ResetGameEvent()
11244 {
11245     if (gameMode == Training)
11246       SetTrainingModeOff();
11247
11248     Reset(TRUE, TRUE);
11249     cmailMsgLoaded = FALSE;
11250     if (appData.icsActive) {
11251       SendToICS(ics_prefix);
11252       SendToICS("refresh\n");
11253     }
11254 }
11255
11256 void
11257 ExitEvent(status)
11258      int status;
11259 {
11260     exiting++;
11261     if (exiting > 2) {
11262       /* Give up on clean exit */
11263       exit(status);
11264     }
11265     if (exiting > 1) {
11266       /* Keep trying for clean exit */
11267       return;
11268     }
11269
11270     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11271
11272     if (telnetISR != NULL) {
11273       RemoveInputSource(telnetISR);
11274     }
11275     if (icsPR != NoProc) {
11276       DestroyChildProcess(icsPR, TRUE);
11277     }
11278
11279     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11280     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11281
11282     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11283     /* make sure this other one finishes before killing it!                  */
11284     if(endingGame) { int count = 0;
11285         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11286         while(endingGame && count++ < 10) DoSleep(1);
11287         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11288     }
11289
11290     /* Kill off chess programs */
11291     if (first.pr != NoProc) {
11292         ExitAnalyzeMode();
11293         
11294         DoSleep( appData.delayBeforeQuit );
11295         SendToProgram("quit\n", &first);
11296         DoSleep( appData.delayAfterQuit );
11297         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11298     }
11299     if (second.pr != NoProc) {
11300         DoSleep( appData.delayBeforeQuit );
11301         SendToProgram("quit\n", &second);
11302         DoSleep( appData.delayAfterQuit );
11303         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11304     }
11305     if (first.isr != NULL) {
11306         RemoveInputSource(first.isr);
11307     }
11308     if (second.isr != NULL) {
11309         RemoveInputSource(second.isr);
11310     }
11311
11312     ShutDownFrontEnd();
11313     exit(status);
11314 }
11315
11316 void
11317 PauseEvent()
11318 {
11319     if (appData.debugMode)
11320         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11321     if (pausing) {
11322         pausing = FALSE;
11323         ModeHighlight();
11324         if (gameMode == MachinePlaysWhite ||
11325             gameMode == MachinePlaysBlack) {
11326             StartClocks();
11327         } else {
11328             DisplayBothClocks();
11329         }
11330         if (gameMode == PlayFromGameFile) {
11331             if (appData.timeDelay >= 0) 
11332                 AutoPlayGameLoop();
11333         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11334             Reset(FALSE, TRUE);
11335             SendToICS(ics_prefix);
11336             SendToICS("refresh\n");
11337         } else if (currentMove < forwardMostMove) {
11338             ForwardInner(forwardMostMove);
11339         }
11340         pauseExamInvalid = FALSE;
11341     } else {
11342         switch (gameMode) {
11343           default:
11344             return;
11345           case IcsExamining:
11346             pauseExamForwardMostMove = forwardMostMove;
11347             pauseExamInvalid = FALSE;
11348             /* fall through */
11349           case IcsObserving:
11350           case IcsPlayingWhite:
11351           case IcsPlayingBlack:
11352             pausing = TRUE;
11353             ModeHighlight();
11354             return;
11355           case PlayFromGameFile:
11356             (void) StopLoadGameTimer();
11357             pausing = TRUE;
11358             ModeHighlight();
11359             break;
11360           case BeginningOfGame:
11361             if (appData.icsActive) return;
11362             /* else fall through */
11363           case MachinePlaysWhite:
11364           case MachinePlaysBlack:
11365           case TwoMachinesPlay:
11366             if (forwardMostMove == 0)
11367               return;           /* don't pause if no one has moved */
11368             if ((gameMode == MachinePlaysWhite &&
11369                  !WhiteOnMove(forwardMostMove)) ||
11370                 (gameMode == MachinePlaysBlack &&
11371                  WhiteOnMove(forwardMostMove))) {
11372                 StopClocks();
11373             }
11374             pausing = TRUE;
11375             ModeHighlight();
11376             break;
11377         }
11378     }
11379 }
11380
11381 void
11382 EditCommentEvent()
11383 {
11384     char title[MSG_SIZ];
11385
11386     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11387         strcpy(title, _("Edit comment"));
11388     } else {
11389         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11390                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11391                 parseList[currentMove - 1]);
11392     }
11393
11394     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11395 }
11396
11397
11398 void
11399 EditTagsEvent()
11400 {
11401     char *tags = PGNTags(&gameInfo);
11402     EditTagsPopUp(tags);
11403     free(tags);
11404 }
11405
11406 void
11407 AnalyzeModeEvent()
11408 {
11409     if (appData.noChessProgram || gameMode == AnalyzeMode)
11410       return;
11411
11412     if (gameMode != AnalyzeFile) {
11413         if (!appData.icsEngineAnalyze) {
11414                EditGameEvent();
11415                if (gameMode != EditGame) return;
11416         }
11417         ResurrectChessProgram();
11418         SendToProgram("analyze\n", &first);
11419         first.analyzing = TRUE;
11420         /*first.maybeThinking = TRUE;*/
11421         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11422         EngineOutputPopUp();
11423     }
11424     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11425     pausing = FALSE;
11426     ModeHighlight();
11427     SetGameInfo();
11428
11429     StartAnalysisClock();
11430     GetTimeMark(&lastNodeCountTime);
11431     lastNodeCount = 0;
11432 }
11433
11434 void
11435 AnalyzeFileEvent()
11436 {
11437     if (appData.noChessProgram || gameMode == AnalyzeFile)
11438       return;
11439
11440     if (gameMode != AnalyzeMode) {
11441         EditGameEvent();
11442         if (gameMode != EditGame) return;
11443         ResurrectChessProgram();
11444         SendToProgram("analyze\n", &first);
11445         first.analyzing = TRUE;
11446         /*first.maybeThinking = TRUE;*/
11447         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11448         EngineOutputPopUp();
11449     }
11450     gameMode = AnalyzeFile;
11451     pausing = FALSE;
11452     ModeHighlight();
11453     SetGameInfo();
11454
11455     StartAnalysisClock();
11456     GetTimeMark(&lastNodeCountTime);
11457     lastNodeCount = 0;
11458 }
11459
11460 void
11461 MachineWhiteEvent()
11462 {
11463     char buf[MSG_SIZ];
11464     char *bookHit = NULL;
11465
11466     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
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 White'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     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11494         gameMode = MachinePlaysWhite;
11495         ResetClocks();
11496     } else
11497     gameMode = MachinePlaysWhite;
11498     pausing = FALSE;
11499     ModeHighlight();
11500     SetGameInfo();
11501     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11502     DisplayTitle(buf);
11503     if (first.sendName) {
11504       sprintf(buf, "name %s\n", gameInfo.black);
11505       SendToProgram(buf, &first);
11506     }
11507     if (first.sendTime) {
11508       if (first.useColors) {
11509         SendToProgram("black\n", &first); /*gnu kludge*/
11510       }
11511       SendTimeRemaining(&first, TRUE);
11512     }
11513     if (first.useColors) {
11514       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11515     }
11516     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11517     SetMachineThinkingEnables();
11518     first.maybeThinking = TRUE;
11519     StartClocks();
11520     firstMove = FALSE;
11521
11522     if (appData.autoFlipView && !flipView) {
11523       flipView = !flipView;
11524       DrawPosition(FALSE, NULL);
11525       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11526     }
11527
11528     if(bookHit) { // [HGM] book: simulate book reply
11529         static char bookMove[MSG_SIZ]; // a bit generous?
11530
11531         programStats.nodes = programStats.depth = programStats.time = 
11532         programStats.score = programStats.got_only_move = 0;
11533         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11534
11535         strcpy(bookMove, "move ");
11536         strcat(bookMove, bookHit);
11537         HandleMachineMove(bookMove, &first);
11538     }
11539 }
11540
11541 void
11542 MachineBlackEvent()
11543 {
11544     char buf[MSG_SIZ];
11545    char *bookHit = NULL;
11546
11547     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11548         return;
11549
11550
11551     if (gameMode == PlayFromGameFile || 
11552         gameMode == TwoMachinesPlay  || 
11553         gameMode == Training         || 
11554         gameMode == AnalyzeMode      || 
11555         gameMode == EndOfGame)
11556         EditGameEvent();
11557
11558     if (gameMode == EditPosition) 
11559         EditPositionDone(TRUE);
11560
11561     if (WhiteOnMove(currentMove)) {
11562         DisplayError(_("It is not Black's turn"), 0);
11563         return;
11564     }
11565     
11566     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11567       ExitAnalyzeMode();
11568
11569     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11570         gameMode == AnalyzeFile)
11571         TruncateGame();
11572
11573     ResurrectChessProgram();    /* in case it isn't running */
11574     gameMode = MachinePlaysBlack;
11575     pausing = FALSE;
11576     ModeHighlight();
11577     SetGameInfo();
11578     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11579     DisplayTitle(buf);
11580     if (first.sendName) {
11581       sprintf(buf, "name %s\n", gameInfo.white);
11582       SendToProgram(buf, &first);
11583     }
11584     if (first.sendTime) {
11585       if (first.useColors) {
11586         SendToProgram("white\n", &first); /*gnu kludge*/
11587       }
11588       SendTimeRemaining(&first, FALSE);
11589     }
11590     if (first.useColors) {
11591       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11592     }
11593     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11594     SetMachineThinkingEnables();
11595     first.maybeThinking = TRUE;
11596     StartClocks();
11597
11598     if (appData.autoFlipView && flipView) {
11599       flipView = !flipView;
11600       DrawPosition(FALSE, NULL);
11601       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11602     }
11603     if(bookHit) { // [HGM] book: simulate book reply
11604         static char bookMove[MSG_SIZ]; // a bit generous?
11605
11606         programStats.nodes = programStats.depth = programStats.time = 
11607         programStats.score = programStats.got_only_move = 0;
11608         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11609
11610         strcpy(bookMove, "move ");
11611         strcat(bookMove, bookHit);
11612         HandleMachineMove(bookMove, &first);
11613     }
11614 }
11615
11616
11617 void
11618 DisplayTwoMachinesTitle()
11619 {
11620     char buf[MSG_SIZ];
11621     if (appData.matchGames > 0) {
11622         if (first.twoMachinesColor[0] == 'w') {
11623             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11624                     gameInfo.white, gameInfo.black,
11625                     first.matchWins, second.matchWins,
11626                     matchGame - 1 - (first.matchWins + second.matchWins));
11627         } else {
11628             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11629                     gameInfo.white, gameInfo.black,
11630                     second.matchWins, first.matchWins,
11631                     matchGame - 1 - (first.matchWins + second.matchWins));
11632         }
11633     } else {
11634         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11635     }
11636     DisplayTitle(buf);
11637 }
11638
11639 void
11640 TwoMachinesEvent P((void))
11641 {
11642     int i;
11643     char buf[MSG_SIZ];
11644     ChessProgramState *onmove;
11645     char *bookHit = NULL;
11646     
11647     if (appData.noChessProgram) return;
11648
11649     switch (gameMode) {
11650       case TwoMachinesPlay:
11651         return;
11652       case MachinePlaysWhite:
11653       case MachinePlaysBlack:
11654         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11655             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11656             return;
11657         }
11658         /* fall through */
11659       case BeginningOfGame:
11660       case PlayFromGameFile:
11661       case EndOfGame:
11662         EditGameEvent();
11663         if (gameMode != EditGame) return;
11664         break;
11665       case EditPosition:
11666         EditPositionDone(TRUE);
11667         break;
11668       case AnalyzeMode:
11669       case AnalyzeFile:
11670         ExitAnalyzeMode();
11671         break;
11672       case EditGame:
11673       default:
11674         break;
11675     }
11676
11677 //    forwardMostMove = currentMove;
11678     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11679     ResurrectChessProgram();    /* in case first program isn't running */
11680
11681     if (second.pr == NULL) {
11682         StartChessProgram(&second);
11683         if (second.protocolVersion == 1) {
11684           TwoMachinesEventIfReady();
11685         } else {
11686           /* kludge: allow timeout for initial "feature" command */
11687           FreezeUI();
11688           DisplayMessage("", _("Starting second chess program"));
11689           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11690         }
11691         return;
11692     }
11693     DisplayMessage("", "");
11694     InitChessProgram(&second, FALSE);
11695     SendToProgram("force\n", &second);
11696     if (startedFromSetupPosition) {
11697         SendBoard(&second, backwardMostMove);
11698     if (appData.debugMode) {
11699         fprintf(debugFP, "Two Machines\n");
11700     }
11701     }
11702     for (i = backwardMostMove; i < forwardMostMove; i++) {
11703         SendMoveToProgram(i, &second);
11704     }
11705
11706     gameMode = TwoMachinesPlay;
11707     pausing = FALSE;
11708     ModeHighlight();
11709     SetGameInfo();
11710     DisplayTwoMachinesTitle();
11711     firstMove = TRUE;
11712     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11713         onmove = &first;
11714     } else {
11715         onmove = &second;
11716     }
11717
11718     SendToProgram(first.computerString, &first);
11719     if (first.sendName) {
11720       sprintf(buf, "name %s\n", second.tidy);
11721       SendToProgram(buf, &first);
11722     }
11723     SendToProgram(second.computerString, &second);
11724     if (second.sendName) {
11725       sprintf(buf, "name %s\n", first.tidy);
11726       SendToProgram(buf, &second);
11727     }
11728
11729     ResetClocks();
11730     if (!first.sendTime || !second.sendTime) {
11731         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11732         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11733     }
11734     if (onmove->sendTime) {
11735       if (onmove->useColors) {
11736         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11737       }
11738       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11739     }
11740     if (onmove->useColors) {
11741       SendToProgram(onmove->twoMachinesColor, onmove);
11742     }
11743     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11744 //    SendToProgram("go\n", onmove);
11745     onmove->maybeThinking = TRUE;
11746     SetMachineThinkingEnables();
11747
11748     StartClocks();
11749
11750     if(bookHit) { // [HGM] book: simulate book reply
11751         static char bookMove[MSG_SIZ]; // a bit generous?
11752
11753         programStats.nodes = programStats.depth = programStats.time = 
11754         programStats.score = programStats.got_only_move = 0;
11755         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11756
11757         strcpy(bookMove, "move ");
11758         strcat(bookMove, bookHit);
11759         savedMessage = bookMove; // args for deferred call
11760         savedState = onmove;
11761         ScheduleDelayedEvent(DeferredBookMove, 1);
11762     }
11763 }
11764
11765 void
11766 TrainingEvent()
11767 {
11768     if (gameMode == Training) {
11769       SetTrainingModeOff();
11770       gameMode = PlayFromGameFile;
11771       DisplayMessage("", _("Training mode off"));
11772     } else {
11773       gameMode = Training;
11774       animateTraining = appData.animate;
11775
11776       /* make sure we are not already at the end of the game */
11777       if (currentMove < forwardMostMove) {
11778         SetTrainingModeOn();
11779         DisplayMessage("", _("Training mode on"));
11780       } else {
11781         gameMode = PlayFromGameFile;
11782         DisplayError(_("Already at end of game"), 0);
11783       }
11784     }
11785     ModeHighlight();
11786 }
11787
11788 void
11789 IcsClientEvent()
11790 {
11791     if (!appData.icsActive) return;
11792     switch (gameMode) {
11793       case IcsPlayingWhite:
11794       case IcsPlayingBlack:
11795       case IcsObserving:
11796       case IcsIdle:
11797       case BeginningOfGame:
11798       case IcsExamining:
11799         return;
11800
11801       case EditGame:
11802         break;
11803
11804       case EditPosition:
11805         EditPositionDone(TRUE);
11806         break;
11807
11808       case AnalyzeMode:
11809       case AnalyzeFile:
11810         ExitAnalyzeMode();
11811         break;
11812         
11813       default:
11814         EditGameEvent();
11815         break;
11816     }
11817
11818     gameMode = IcsIdle;
11819     ModeHighlight();
11820     return;
11821 }
11822
11823
11824 void
11825 EditGameEvent()
11826 {
11827     int i;
11828
11829     switch (gameMode) {
11830       case Training:
11831         SetTrainingModeOff();
11832         break;
11833       case MachinePlaysWhite:
11834       case MachinePlaysBlack:
11835       case BeginningOfGame:
11836         SendToProgram("force\n", &first);
11837         SetUserThinkingEnables();
11838         break;
11839       case PlayFromGameFile:
11840         (void) StopLoadGameTimer();
11841         if (gameFileFP != NULL) {
11842             gameFileFP = NULL;
11843         }
11844         break;
11845       case EditPosition:
11846         EditPositionDone(TRUE);
11847         break;
11848       case AnalyzeMode:
11849       case AnalyzeFile:
11850         ExitAnalyzeMode();
11851         SendToProgram("force\n", &first);
11852         break;
11853       case TwoMachinesPlay:
11854         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11855         ResurrectChessProgram();
11856         SetUserThinkingEnables();
11857         break;
11858       case EndOfGame:
11859         ResurrectChessProgram();
11860         break;
11861       case IcsPlayingBlack:
11862       case IcsPlayingWhite:
11863         DisplayError(_("Warning: You are still playing a game"), 0);
11864         break;
11865       case IcsObserving:
11866         DisplayError(_("Warning: You are still observing a game"), 0);
11867         break;
11868       case IcsExamining:
11869         DisplayError(_("Warning: You are still examining a game"), 0);
11870         break;
11871       case IcsIdle:
11872         break;
11873       case EditGame:
11874       default:
11875         return;
11876     }
11877     
11878     pausing = FALSE;
11879     StopClocks();
11880     first.offeredDraw = second.offeredDraw = 0;
11881
11882     if (gameMode == PlayFromGameFile) {
11883         whiteTimeRemaining = timeRemaining[0][currentMove];
11884         blackTimeRemaining = timeRemaining[1][currentMove];
11885         DisplayTitle("");
11886     }
11887
11888     if (gameMode == MachinePlaysWhite ||
11889         gameMode == MachinePlaysBlack ||
11890         gameMode == TwoMachinesPlay ||
11891         gameMode == EndOfGame) {
11892         i = forwardMostMove;
11893         while (i > currentMove) {
11894             SendToProgram("undo\n", &first);
11895             i--;
11896         }
11897         whiteTimeRemaining = timeRemaining[0][currentMove];
11898         blackTimeRemaining = timeRemaining[1][currentMove];
11899         DisplayBothClocks();
11900         if (whiteFlag || blackFlag) {
11901             whiteFlag = blackFlag = 0;
11902         }
11903         DisplayTitle("");
11904     }           
11905     
11906     gameMode = EditGame;
11907     ModeHighlight();
11908     SetGameInfo();
11909 }
11910
11911
11912 void
11913 EditPositionEvent()
11914 {
11915     if (gameMode == EditPosition) {
11916         EditGameEvent();
11917         return;
11918     }
11919     
11920     EditGameEvent();
11921     if (gameMode != EditGame) return;
11922     
11923     gameMode = EditPosition;
11924     ModeHighlight();
11925     SetGameInfo();
11926     if (currentMove > 0)
11927       CopyBoard(boards[0], boards[currentMove]);
11928     
11929     blackPlaysFirst = !WhiteOnMove(currentMove);
11930     ResetClocks();
11931     currentMove = forwardMostMove = backwardMostMove = 0;
11932     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11933     DisplayMove(-1);
11934 }
11935
11936 void
11937 ExitAnalyzeMode()
11938 {
11939     /* [DM] icsEngineAnalyze - possible call from other functions */
11940     if (appData.icsEngineAnalyze) {
11941         appData.icsEngineAnalyze = FALSE;
11942
11943         DisplayMessage("",_("Close ICS engine analyze..."));
11944     }
11945     if (first.analysisSupport && first.analyzing) {
11946       SendToProgram("exit\n", &first);
11947       first.analyzing = FALSE;
11948     }
11949     thinkOutput[0] = NULLCHAR;
11950 }
11951
11952 void
11953 EditPositionDone(Boolean fakeRights)
11954 {
11955     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11956
11957     startedFromSetupPosition = TRUE;
11958     InitChessProgram(&first, FALSE);
11959     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11960       boards[0][EP_STATUS] = EP_NONE;
11961       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11962     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11963         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11964         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11965       } else boards[0][CASTLING][2] = NoRights;
11966     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11967         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11968         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11969       } else boards[0][CASTLING][5] = NoRights;
11970     }
11971     SendToProgram("force\n", &first);
11972     if (blackPlaysFirst) {
11973         strcpy(moveList[0], "");
11974         strcpy(parseList[0], "");
11975         currentMove = forwardMostMove = backwardMostMove = 1;
11976         CopyBoard(boards[1], boards[0]);
11977     } else {
11978         currentMove = forwardMostMove = backwardMostMove = 0;
11979     }
11980     SendBoard(&first, forwardMostMove);
11981     if (appData.debugMode) {
11982         fprintf(debugFP, "EditPosDone\n");
11983     }
11984     DisplayTitle("");
11985     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11986     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11987     gameMode = EditGame;
11988     ModeHighlight();
11989     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11990     ClearHighlights(); /* [AS] */
11991 }
11992
11993 /* Pause for `ms' milliseconds */
11994 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11995 void
11996 TimeDelay(ms)
11997      long ms;
11998 {
11999     TimeMark m1, m2;
12000
12001     GetTimeMark(&m1);
12002     do {
12003         GetTimeMark(&m2);
12004     } while (SubtractTimeMarks(&m2, &m1) < ms);
12005 }
12006
12007 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12008 void
12009 SendMultiLineToICS(buf)
12010      char *buf;
12011 {
12012     char temp[MSG_SIZ+1], *p;
12013     int len;
12014
12015     len = strlen(buf);
12016     if (len > MSG_SIZ)
12017       len = MSG_SIZ;
12018   
12019     strncpy(temp, buf, len);
12020     temp[len] = 0;
12021
12022     p = temp;
12023     while (*p) {
12024         if (*p == '\n' || *p == '\r')
12025           *p = ' ';
12026         ++p;
12027     }
12028
12029     strcat(temp, "\n");
12030     SendToICS(temp);
12031     SendToPlayer(temp, strlen(temp));
12032 }
12033
12034 void
12035 SetWhiteToPlayEvent()
12036 {
12037     if (gameMode == EditPosition) {
12038         blackPlaysFirst = FALSE;
12039         DisplayBothClocks();    /* works because currentMove is 0 */
12040     } else if (gameMode == IcsExamining) {
12041         SendToICS(ics_prefix);
12042         SendToICS("tomove white\n");
12043     }
12044 }
12045
12046 void
12047 SetBlackToPlayEvent()
12048 {
12049     if (gameMode == EditPosition) {
12050         blackPlaysFirst = TRUE;
12051         currentMove = 1;        /* kludge */
12052         DisplayBothClocks();
12053         currentMove = 0;
12054     } else if (gameMode == IcsExamining) {
12055         SendToICS(ics_prefix);
12056         SendToICS("tomove black\n");
12057     }
12058 }
12059
12060 void
12061 EditPositionMenuEvent(selection, x, y)
12062      ChessSquare selection;
12063      int x, y;
12064 {
12065     char buf[MSG_SIZ];
12066     ChessSquare piece = boards[0][y][x];
12067
12068     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12069
12070     switch (selection) {
12071       case ClearBoard:
12072         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12073             SendToICS(ics_prefix);
12074             SendToICS("bsetup clear\n");
12075         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12076             SendToICS(ics_prefix);
12077             SendToICS("clearboard\n");
12078         } else {
12079             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12080                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12081                 for (y = 0; y < BOARD_HEIGHT; y++) {
12082                     if (gameMode == IcsExamining) {
12083                         if (boards[currentMove][y][x] != EmptySquare) {
12084                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12085                                     AAA + x, ONE + y);
12086                             SendToICS(buf);
12087                         }
12088                     } else {
12089                         boards[0][y][x] = p;
12090                     }
12091                 }
12092             }
12093         }
12094         if (gameMode == EditPosition) {
12095             DrawPosition(FALSE, boards[0]);
12096         }
12097         break;
12098
12099       case WhitePlay:
12100         SetWhiteToPlayEvent();
12101         break;
12102
12103       case BlackPlay:
12104         SetBlackToPlayEvent();
12105         break;
12106
12107       case EmptySquare:
12108         if (gameMode == IcsExamining) {
12109             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12110             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12111             SendToICS(buf);
12112         } else {
12113             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12114                 if(x == BOARD_LEFT-2) {
12115                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12116                     boards[0][y][1] = 0;
12117                 } else
12118                 if(x == BOARD_RGHT+1) {
12119                     if(y >= gameInfo.holdingsSize) break;
12120                     boards[0][y][BOARD_WIDTH-2] = 0;
12121                 } else break;
12122             }
12123             boards[0][y][x] = EmptySquare;
12124             DrawPosition(FALSE, boards[0]);
12125         }
12126         break;
12127
12128       case PromotePiece:
12129         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12130            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12131             selection = (ChessSquare) (PROMOTED piece);
12132         } else if(piece == EmptySquare) selection = WhiteSilver;
12133         else selection = (ChessSquare)((int)piece - 1);
12134         goto defaultlabel;
12135
12136       case DemotePiece:
12137         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12138            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12139             selection = (ChessSquare) (DEMOTED piece);
12140         } else if(piece == EmptySquare) selection = BlackSilver;
12141         else selection = (ChessSquare)((int)piece + 1);       
12142         goto defaultlabel;
12143
12144       case WhiteQueen:
12145       case BlackQueen:
12146         if(gameInfo.variant == VariantShatranj ||
12147            gameInfo.variant == VariantXiangqi  ||
12148            gameInfo.variant == VariantCourier  ||
12149            gameInfo.variant == VariantMakruk     )
12150             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12151         goto defaultlabel;
12152
12153       case WhiteKing:
12154       case BlackKing:
12155         if(gameInfo.variant == VariantXiangqi)
12156             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12157         if(gameInfo.variant == VariantKnightmate)
12158             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12159       default:
12160         defaultlabel:
12161         if (gameMode == IcsExamining) {
12162             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12163             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12164                     PieceToChar(selection), AAA + x, ONE + y);
12165             SendToICS(buf);
12166         } else {
12167             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12168                 int n;
12169                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12170                     n = PieceToNumber(selection - BlackPawn);
12171                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12172                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12173                     boards[0][BOARD_HEIGHT-1-n][1]++;
12174                 } else
12175                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12176                     n = PieceToNumber(selection);
12177                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12178                     boards[0][n][BOARD_WIDTH-1] = selection;
12179                     boards[0][n][BOARD_WIDTH-2]++;
12180                 }
12181             } else
12182             boards[0][y][x] = selection;
12183             DrawPosition(TRUE, boards[0]);
12184         }
12185         break;
12186     }
12187 }
12188
12189
12190 void
12191 DropMenuEvent(selection, x, y)
12192      ChessSquare selection;
12193      int x, y;
12194 {
12195     ChessMove moveType;
12196
12197     switch (gameMode) {
12198       case IcsPlayingWhite:
12199       case MachinePlaysBlack:
12200         if (!WhiteOnMove(currentMove)) {
12201             DisplayMoveError(_("It is Black's turn"));
12202             return;
12203         }
12204         moveType = WhiteDrop;
12205         break;
12206       case IcsPlayingBlack:
12207       case MachinePlaysWhite:
12208         if (WhiteOnMove(currentMove)) {
12209             DisplayMoveError(_("It is White's turn"));
12210             return;
12211         }
12212         moveType = BlackDrop;
12213         break;
12214       case EditGame:
12215         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12216         break;
12217       default:
12218         return;
12219     }
12220
12221     if (moveType == BlackDrop && selection < BlackPawn) {
12222       selection = (ChessSquare) ((int) selection
12223                                  + (int) BlackPawn - (int) WhitePawn);
12224     }
12225     if (boards[currentMove][y][x] != EmptySquare) {
12226         DisplayMoveError(_("That square is occupied"));
12227         return;
12228     }
12229
12230     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12231 }
12232
12233 void
12234 AcceptEvent()
12235 {
12236     /* Accept a pending offer of any kind from opponent */
12237     
12238     if (appData.icsActive) {
12239         SendToICS(ics_prefix);
12240         SendToICS("accept\n");
12241     } else if (cmailMsgLoaded) {
12242         if (currentMove == cmailOldMove &&
12243             commentList[cmailOldMove] != NULL &&
12244             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12245                    "Black offers a draw" : "White offers a draw")) {
12246             TruncateGame();
12247             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12248             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12249         } else {
12250             DisplayError(_("There is no pending offer on this move"), 0);
12251             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12252         }
12253     } else {
12254         /* Not used for offers from chess program */
12255     }
12256 }
12257
12258 void
12259 DeclineEvent()
12260 {
12261     /* Decline a pending offer of any kind from opponent */
12262     
12263     if (appData.icsActive) {
12264         SendToICS(ics_prefix);
12265         SendToICS("decline\n");
12266     } else if (cmailMsgLoaded) {
12267         if (currentMove == cmailOldMove &&
12268             commentList[cmailOldMove] != NULL &&
12269             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12270                    "Black offers a draw" : "White offers a draw")) {
12271 #ifdef NOTDEF
12272             AppendComment(cmailOldMove, "Draw declined", TRUE);
12273             DisplayComment(cmailOldMove - 1, "Draw declined");
12274 #endif /*NOTDEF*/
12275         } else {
12276             DisplayError(_("There is no pending offer on this move"), 0);
12277         }
12278     } else {
12279         /* Not used for offers from chess program */
12280     }
12281 }
12282
12283 void
12284 RematchEvent()
12285 {
12286     /* Issue ICS rematch command */
12287     if (appData.icsActive) {
12288         SendToICS(ics_prefix);
12289         SendToICS("rematch\n");
12290     }
12291 }
12292
12293 void
12294 CallFlagEvent()
12295 {
12296     /* Call your opponent's flag (claim a win on time) */
12297     if (appData.icsActive) {
12298         SendToICS(ics_prefix);
12299         SendToICS("flag\n");
12300     } else {
12301         switch (gameMode) {
12302           default:
12303             return;
12304           case MachinePlaysWhite:
12305             if (whiteFlag) {
12306                 if (blackFlag)
12307                   GameEnds(GameIsDrawn, "Both players ran out of time",
12308                            GE_PLAYER);
12309                 else
12310                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12311             } else {
12312                 DisplayError(_("Your opponent is not out of time"), 0);
12313             }
12314             break;
12315           case MachinePlaysBlack:
12316             if (blackFlag) {
12317                 if (whiteFlag)
12318                   GameEnds(GameIsDrawn, "Both players ran out of time",
12319                            GE_PLAYER);
12320                 else
12321                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12322             } else {
12323                 DisplayError(_("Your opponent is not out of time"), 0);
12324             }
12325             break;
12326         }
12327     }
12328 }
12329
12330 void
12331 DrawEvent()
12332 {
12333     /* Offer draw or accept pending draw offer from opponent */
12334     
12335     if (appData.icsActive) {
12336         /* Note: tournament rules require draw offers to be
12337            made after you make your move but before you punch
12338            your clock.  Currently ICS doesn't let you do that;
12339            instead, you immediately punch your clock after making
12340            a move, but you can offer a draw at any time. */
12341         
12342         SendToICS(ics_prefix);
12343         SendToICS("draw\n");
12344         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12345     } else if (cmailMsgLoaded) {
12346         if (currentMove == cmailOldMove &&
12347             commentList[cmailOldMove] != NULL &&
12348             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12349                    "Black offers a draw" : "White offers a draw")) {
12350             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12351             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12352         } else if (currentMove == cmailOldMove + 1) {
12353             char *offer = WhiteOnMove(cmailOldMove) ?
12354               "White offers a draw" : "Black offers a draw";
12355             AppendComment(currentMove, offer, TRUE);
12356             DisplayComment(currentMove - 1, offer);
12357             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12358         } else {
12359             DisplayError(_("You must make your move before offering a draw"), 0);
12360             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12361         }
12362     } else if (first.offeredDraw) {
12363         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12364     } else {
12365         if (first.sendDrawOffers) {
12366             SendToProgram("draw\n", &first);
12367             userOfferedDraw = TRUE;
12368         }
12369     }
12370 }
12371
12372 void
12373 AdjournEvent()
12374 {
12375     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12376     
12377     if (appData.icsActive) {
12378         SendToICS(ics_prefix);
12379         SendToICS("adjourn\n");
12380     } else {
12381         /* Currently GNU Chess doesn't offer or accept Adjourns */
12382     }
12383 }
12384
12385
12386 void
12387 AbortEvent()
12388 {
12389     /* Offer Abort or accept pending Abort offer from opponent */
12390     
12391     if (appData.icsActive) {
12392         SendToICS(ics_prefix);
12393         SendToICS("abort\n");
12394     } else {
12395         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12396     }
12397 }
12398
12399 void
12400 ResignEvent()
12401 {
12402     /* Resign.  You can do this even if it's not your turn. */
12403     
12404     if (appData.icsActive) {
12405         SendToICS(ics_prefix);
12406         SendToICS("resign\n");
12407     } else {
12408         switch (gameMode) {
12409           case MachinePlaysWhite:
12410             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12411             break;
12412           case MachinePlaysBlack:
12413             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12414             break;
12415           case EditGame:
12416             if (cmailMsgLoaded) {
12417                 TruncateGame();
12418                 if (WhiteOnMove(cmailOldMove)) {
12419                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12420                 } else {
12421                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12422                 }
12423                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12424             }
12425             break;
12426           default:
12427             break;
12428         }
12429     }
12430 }
12431
12432
12433 void
12434 StopObservingEvent()
12435 {
12436     /* Stop observing current games */
12437     SendToICS(ics_prefix);
12438     SendToICS("unobserve\n");
12439 }
12440
12441 void
12442 StopExaminingEvent()
12443 {
12444     /* Stop observing current game */
12445     SendToICS(ics_prefix);
12446     SendToICS("unexamine\n");
12447 }
12448
12449 void
12450 ForwardInner(target)
12451      int target;
12452 {
12453     int limit;
12454
12455     if (appData.debugMode)
12456         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12457                 target, currentMove, forwardMostMove);
12458
12459     if (gameMode == EditPosition)
12460       return;
12461
12462     if (gameMode == PlayFromGameFile && !pausing)
12463       PauseEvent();
12464     
12465     if (gameMode == IcsExamining && pausing)
12466       limit = pauseExamForwardMostMove;
12467     else
12468       limit = forwardMostMove;
12469     
12470     if (target > limit) target = limit;
12471
12472     if (target > 0 && moveList[target - 1][0]) {
12473         int fromX, fromY, toX, toY;
12474         toX = moveList[target - 1][2] - AAA;
12475         toY = moveList[target - 1][3] - ONE;
12476         if (moveList[target - 1][1] == '@') {
12477             if (appData.highlightLastMove) {
12478                 SetHighlights(-1, -1, toX, toY);
12479             }
12480         } else {
12481             fromX = moveList[target - 1][0] - AAA;
12482             fromY = moveList[target - 1][1] - ONE;
12483             if (target == currentMove + 1) {
12484                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12485             }
12486             if (appData.highlightLastMove) {
12487                 SetHighlights(fromX, fromY, toX, toY);
12488             }
12489         }
12490     }
12491     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12492         gameMode == Training || gameMode == PlayFromGameFile || 
12493         gameMode == AnalyzeFile) {
12494         while (currentMove < target) {
12495             SendMoveToProgram(currentMove++, &first);
12496         }
12497     } else {
12498         currentMove = target;
12499     }
12500     
12501     if (gameMode == EditGame || gameMode == EndOfGame) {
12502         whiteTimeRemaining = timeRemaining[0][currentMove];
12503         blackTimeRemaining = timeRemaining[1][currentMove];
12504     }
12505     DisplayBothClocks();
12506     DisplayMove(currentMove - 1);
12507     DrawPosition(FALSE, boards[currentMove]);
12508     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12509     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12510         DisplayComment(currentMove - 1, commentList[currentMove]);
12511     }
12512 }
12513
12514
12515 void
12516 ForwardEvent()
12517 {
12518     if (gameMode == IcsExamining && !pausing) {
12519         SendToICS(ics_prefix);
12520         SendToICS("forward\n");
12521     } else {
12522         ForwardInner(currentMove + 1);
12523     }
12524 }
12525
12526 void
12527 ToEndEvent()
12528 {
12529     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12530         /* to optimze, we temporarily turn off analysis mode while we feed
12531          * the remaining moves to the engine. Otherwise we get analysis output
12532          * after each move.
12533          */ 
12534         if (first.analysisSupport) {
12535           SendToProgram("exit\nforce\n", &first);
12536           first.analyzing = FALSE;
12537         }
12538     }
12539         
12540     if (gameMode == IcsExamining && !pausing) {
12541         SendToICS(ics_prefix);
12542         SendToICS("forward 999999\n");
12543     } else {
12544         ForwardInner(forwardMostMove);
12545     }
12546
12547     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12548         /* we have fed all the moves, so reactivate analysis mode */
12549         SendToProgram("analyze\n", &first);
12550         first.analyzing = TRUE;
12551         /*first.maybeThinking = TRUE;*/
12552         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12553     }
12554 }
12555
12556 void
12557 BackwardInner(target)
12558      int target;
12559 {
12560     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12561
12562     if (appData.debugMode)
12563         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12564                 target, currentMove, forwardMostMove);
12565
12566     if (gameMode == EditPosition) return;
12567     if (currentMove <= backwardMostMove) {
12568         ClearHighlights();
12569         DrawPosition(full_redraw, boards[currentMove]);
12570         return;
12571     }
12572     if (gameMode == PlayFromGameFile && !pausing)
12573       PauseEvent();
12574     
12575     if (moveList[target][0]) {
12576         int fromX, fromY, toX, toY;
12577         toX = moveList[target][2] - AAA;
12578         toY = moveList[target][3] - ONE;
12579         if (moveList[target][1] == '@') {
12580             if (appData.highlightLastMove) {
12581                 SetHighlights(-1, -1, toX, toY);
12582             }
12583         } else {
12584             fromX = moveList[target][0] - AAA;
12585             fromY = moveList[target][1] - ONE;
12586             if (target == currentMove - 1) {
12587                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12588             }
12589             if (appData.highlightLastMove) {
12590                 SetHighlights(fromX, fromY, toX, toY);
12591             }
12592         }
12593     }
12594     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12595         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12596         while (currentMove > target) {
12597             SendToProgram("undo\n", &first);
12598             currentMove--;
12599         }
12600     } else {
12601         currentMove = target;
12602     }
12603     
12604     if (gameMode == EditGame || gameMode == EndOfGame) {
12605         whiteTimeRemaining = timeRemaining[0][currentMove];
12606         blackTimeRemaining = timeRemaining[1][currentMove];
12607     }
12608     DisplayBothClocks();
12609     DisplayMove(currentMove - 1);
12610     DrawPosition(full_redraw, boards[currentMove]);
12611     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12612     // [HGM] PV info: routine tests if comment empty
12613     DisplayComment(currentMove - 1, commentList[currentMove]);
12614 }
12615
12616 void
12617 BackwardEvent()
12618 {
12619     if (gameMode == IcsExamining && !pausing) {
12620         SendToICS(ics_prefix);
12621         SendToICS("backward\n");
12622     } else {
12623         BackwardInner(currentMove - 1);
12624     }
12625 }
12626
12627 void
12628 ToStartEvent()
12629 {
12630     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12631         /* to optimize, we temporarily turn off analysis mode while we undo
12632          * all the moves. Otherwise we get analysis output after each undo.
12633          */ 
12634         if (first.analysisSupport) {
12635           SendToProgram("exit\nforce\n", &first);
12636           first.analyzing = FALSE;
12637         }
12638     }
12639
12640     if (gameMode == IcsExamining && !pausing) {
12641         SendToICS(ics_prefix);
12642         SendToICS("backward 999999\n");
12643     } else {
12644         BackwardInner(backwardMostMove);
12645     }
12646
12647     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12648         /* we have fed all the moves, so reactivate analysis mode */
12649         SendToProgram("analyze\n", &first);
12650         first.analyzing = TRUE;
12651         /*first.maybeThinking = TRUE;*/
12652         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12653     }
12654 }
12655
12656 void
12657 ToNrEvent(int to)
12658 {
12659   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12660   if (to >= forwardMostMove) to = forwardMostMove;
12661   if (to <= backwardMostMove) to = backwardMostMove;
12662   if (to < currentMove) {
12663     BackwardInner(to);
12664   } else {
12665     ForwardInner(to);
12666   }
12667 }
12668
12669 void
12670 RevertEvent()
12671 {
12672     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12673         return;
12674     }
12675     if (gameMode != IcsExamining) {
12676         DisplayError(_("You are not examining a game"), 0);
12677         return;
12678     }
12679     if (pausing) {
12680         DisplayError(_("You can't revert while pausing"), 0);
12681         return;
12682     }
12683     SendToICS(ics_prefix);
12684     SendToICS("revert\n");
12685 }
12686
12687 void
12688 RetractMoveEvent()
12689 {
12690     switch (gameMode) {
12691       case MachinePlaysWhite:
12692       case MachinePlaysBlack:
12693         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12694             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12695             return;
12696         }
12697         if (forwardMostMove < 2) return;
12698         currentMove = forwardMostMove = forwardMostMove - 2;
12699         whiteTimeRemaining = timeRemaining[0][currentMove];
12700         blackTimeRemaining = timeRemaining[1][currentMove];
12701         DisplayBothClocks();
12702         DisplayMove(currentMove - 1);
12703         ClearHighlights();/*!! could figure this out*/
12704         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12705         SendToProgram("remove\n", &first);
12706         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12707         break;
12708
12709       case BeginningOfGame:
12710       default:
12711         break;
12712
12713       case IcsPlayingWhite:
12714       case IcsPlayingBlack:
12715         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12716             SendToICS(ics_prefix);
12717             SendToICS("takeback 2\n");
12718         } else {
12719             SendToICS(ics_prefix);
12720             SendToICS("takeback 1\n");
12721         }
12722         break;
12723     }
12724 }
12725
12726 void
12727 MoveNowEvent()
12728 {
12729     ChessProgramState *cps;
12730
12731     switch (gameMode) {
12732       case MachinePlaysWhite:
12733         if (!WhiteOnMove(forwardMostMove)) {
12734             DisplayError(_("It is your turn"), 0);
12735             return;
12736         }
12737         cps = &first;
12738         break;
12739       case MachinePlaysBlack:
12740         if (WhiteOnMove(forwardMostMove)) {
12741             DisplayError(_("It is your turn"), 0);
12742             return;
12743         }
12744         cps = &first;
12745         break;
12746       case TwoMachinesPlay:
12747         if (WhiteOnMove(forwardMostMove) ==
12748             (first.twoMachinesColor[0] == 'w')) {
12749             cps = &first;
12750         } else {
12751             cps = &second;
12752         }
12753         break;
12754       case BeginningOfGame:
12755       default:
12756         return;
12757     }
12758     SendToProgram("?\n", cps);
12759 }
12760
12761 void
12762 TruncateGameEvent()
12763 {
12764     EditGameEvent();
12765     if (gameMode != EditGame) return;
12766     TruncateGame();
12767 }
12768
12769 void
12770 TruncateGame()
12771 {
12772     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12773     if (forwardMostMove > currentMove) {
12774         if (gameInfo.resultDetails != NULL) {
12775             free(gameInfo.resultDetails);
12776             gameInfo.resultDetails = NULL;
12777             gameInfo.result = GameUnfinished;
12778         }
12779         forwardMostMove = currentMove;
12780         HistorySet(parseList, backwardMostMove, forwardMostMove,
12781                    currentMove-1);
12782     }
12783 }
12784
12785 void
12786 HintEvent()
12787 {
12788     if (appData.noChessProgram) return;
12789     switch (gameMode) {
12790       case MachinePlaysWhite:
12791         if (WhiteOnMove(forwardMostMove)) {
12792             DisplayError(_("Wait until your turn"), 0);
12793             return;
12794         }
12795         break;
12796       case BeginningOfGame:
12797       case MachinePlaysBlack:
12798         if (!WhiteOnMove(forwardMostMove)) {
12799             DisplayError(_("Wait until your turn"), 0);
12800             return;
12801         }
12802         break;
12803       default:
12804         DisplayError(_("No hint available"), 0);
12805         return;
12806     }
12807     SendToProgram("hint\n", &first);
12808     hintRequested = TRUE;
12809 }
12810
12811 void
12812 BookEvent()
12813 {
12814     if (appData.noChessProgram) return;
12815     switch (gameMode) {
12816       case MachinePlaysWhite:
12817         if (WhiteOnMove(forwardMostMove)) {
12818             DisplayError(_("Wait until your turn"), 0);
12819             return;
12820         }
12821         break;
12822       case BeginningOfGame:
12823       case MachinePlaysBlack:
12824         if (!WhiteOnMove(forwardMostMove)) {
12825             DisplayError(_("Wait until your turn"), 0);
12826             return;
12827         }
12828         break;
12829       case EditPosition:
12830         EditPositionDone(TRUE);
12831         break;
12832       case TwoMachinesPlay:
12833         return;
12834       default:
12835         break;
12836     }
12837     SendToProgram("bk\n", &first);
12838     bookOutput[0] = NULLCHAR;
12839     bookRequested = TRUE;
12840 }
12841
12842 void
12843 AboutGameEvent()
12844 {
12845     char *tags = PGNTags(&gameInfo);
12846     TagsPopUp(tags, CmailMsg());
12847     free(tags);
12848 }
12849
12850 /* end button procedures */
12851
12852 void
12853 PrintPosition(fp, move)
12854      FILE *fp;
12855      int move;
12856 {
12857     int i, j;
12858     
12859     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12860         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12861             char c = PieceToChar(boards[move][i][j]);
12862             fputc(c == 'x' ? '.' : c, fp);
12863             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12864         }
12865     }
12866     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12867       fprintf(fp, "white to play\n");
12868     else
12869       fprintf(fp, "black to play\n");
12870 }
12871
12872 void
12873 PrintOpponents(fp)
12874      FILE *fp;
12875 {
12876     if (gameInfo.white != NULL) {
12877         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12878     } else {
12879         fprintf(fp, "\n");
12880     }
12881 }
12882
12883 /* Find last component of program's own name, using some heuristics */
12884 void
12885 TidyProgramName(prog, host, buf)
12886      char *prog, *host, buf[MSG_SIZ];
12887 {
12888     char *p, *q;
12889     int local = (strcmp(host, "localhost") == 0);
12890     while (!local && (p = strchr(prog, ';')) != NULL) {
12891         p++;
12892         while (*p == ' ') p++;
12893         prog = p;
12894     }
12895     if (*prog == '"' || *prog == '\'') {
12896         q = strchr(prog + 1, *prog);
12897     } else {
12898         q = strchr(prog, ' ');
12899     }
12900     if (q == NULL) q = prog + strlen(prog);
12901     p = q;
12902     while (p >= prog && *p != '/' && *p != '\\') p--;
12903     p++;
12904     if(p == prog && *p == '"') p++;
12905     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12906     memcpy(buf, p, q - p);
12907     buf[q - p] = NULLCHAR;
12908     if (!local) {
12909         strcat(buf, "@");
12910         strcat(buf, host);
12911     }
12912 }
12913
12914 char *
12915 TimeControlTagValue()
12916 {
12917     char buf[MSG_SIZ];
12918     if (!appData.clockMode) {
12919         strcpy(buf, "-");
12920     } else if (movesPerSession > 0) {
12921         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12922     } else if (timeIncrement == 0) {
12923         sprintf(buf, "%ld", timeControl/1000);
12924     } else {
12925         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12926     }
12927     return StrSave(buf);
12928 }
12929
12930 void
12931 SetGameInfo()
12932 {
12933     /* This routine is used only for certain modes */
12934     VariantClass v = gameInfo.variant;
12935     ChessMove r = GameUnfinished;
12936     char *p = NULL;
12937
12938     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12939         r = gameInfo.result; 
12940         p = gameInfo.resultDetails; 
12941         gameInfo.resultDetails = NULL;
12942     }
12943     ClearGameInfo(&gameInfo);
12944     gameInfo.variant = v;
12945
12946     switch (gameMode) {
12947       case MachinePlaysWhite:
12948         gameInfo.event = StrSave( appData.pgnEventHeader );
12949         gameInfo.site = StrSave(HostName());
12950         gameInfo.date = PGNDate();
12951         gameInfo.round = StrSave("-");
12952         gameInfo.white = StrSave(first.tidy);
12953         gameInfo.black = StrSave(UserName());
12954         gameInfo.timeControl = TimeControlTagValue();
12955         break;
12956
12957       case MachinePlaysBlack:
12958         gameInfo.event = StrSave( appData.pgnEventHeader );
12959         gameInfo.site = StrSave(HostName());
12960         gameInfo.date = PGNDate();
12961         gameInfo.round = StrSave("-");
12962         gameInfo.white = StrSave(UserName());
12963         gameInfo.black = StrSave(first.tidy);
12964         gameInfo.timeControl = TimeControlTagValue();
12965         break;
12966
12967       case TwoMachinesPlay:
12968         gameInfo.event = StrSave( appData.pgnEventHeader );
12969         gameInfo.site = StrSave(HostName());
12970         gameInfo.date = PGNDate();
12971         if (matchGame > 0) {
12972             char buf[MSG_SIZ];
12973             sprintf(buf, "%d", matchGame);
12974             gameInfo.round = StrSave(buf);
12975         } else {
12976             gameInfo.round = StrSave("-");
12977         }
12978         if (first.twoMachinesColor[0] == 'w') {
12979             gameInfo.white = StrSave(first.tidy);
12980             gameInfo.black = StrSave(second.tidy);
12981         } else {
12982             gameInfo.white = StrSave(second.tidy);
12983             gameInfo.black = StrSave(first.tidy);
12984         }
12985         gameInfo.timeControl = TimeControlTagValue();
12986         break;
12987
12988       case EditGame:
12989         gameInfo.event = StrSave("Edited game");
12990         gameInfo.site = StrSave(HostName());
12991         gameInfo.date = PGNDate();
12992         gameInfo.round = StrSave("-");
12993         gameInfo.white = StrSave("-");
12994         gameInfo.black = StrSave("-");
12995         gameInfo.result = r;
12996         gameInfo.resultDetails = p;
12997         break;
12998
12999       case EditPosition:
13000         gameInfo.event = StrSave("Edited position");
13001         gameInfo.site = StrSave(HostName());
13002         gameInfo.date = PGNDate();
13003         gameInfo.round = StrSave("-");
13004         gameInfo.white = StrSave("-");
13005         gameInfo.black = StrSave("-");
13006         break;
13007
13008       case IcsPlayingWhite:
13009       case IcsPlayingBlack:
13010       case IcsObserving:
13011       case IcsExamining:
13012         break;
13013
13014       case PlayFromGameFile:
13015         gameInfo.event = StrSave("Game from non-PGN file");
13016         gameInfo.site = StrSave(HostName());
13017         gameInfo.date = PGNDate();
13018         gameInfo.round = StrSave("-");
13019         gameInfo.white = StrSave("?");
13020         gameInfo.black = StrSave("?");
13021         break;
13022
13023       default:
13024         break;
13025     }
13026 }
13027
13028 void
13029 ReplaceComment(index, text)
13030      int index;
13031      char *text;
13032 {
13033     int len;
13034
13035     while (*text == '\n') text++;
13036     len = strlen(text);
13037     while (len > 0 && text[len - 1] == '\n') len--;
13038
13039     if (commentList[index] != NULL)
13040       free(commentList[index]);
13041
13042     if (len == 0) {
13043         commentList[index] = NULL;
13044         return;
13045     }
13046   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13047       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13048       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13049     commentList[index] = (char *) malloc(len + 2);
13050     strncpy(commentList[index], text, len);
13051     commentList[index][len] = '\n';
13052     commentList[index][len + 1] = NULLCHAR;
13053   } else { 
13054     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13055     char *p;
13056     commentList[index] = (char *) malloc(len + 6);
13057     strcpy(commentList[index], "{\n");
13058     strncpy(commentList[index]+2, text, len);
13059     commentList[index][len+2] = NULLCHAR;
13060     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13061     strcat(commentList[index], "\n}\n");
13062   }
13063 }
13064
13065 void
13066 CrushCRs(text)
13067      char *text;
13068 {
13069   char *p = text;
13070   char *q = text;
13071   char ch;
13072
13073   do {
13074     ch = *p++;
13075     if (ch == '\r') continue;
13076     *q++ = ch;
13077   } while (ch != '\0');
13078 }
13079
13080 void
13081 AppendComment(index, text, addBraces)
13082      int index;
13083      char *text;
13084      Boolean addBraces; // [HGM] braces: tells if we should add {}
13085 {
13086     int oldlen, len;
13087     char *old;
13088
13089 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13090     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13091
13092     CrushCRs(text);
13093     while (*text == '\n') text++;
13094     len = strlen(text);
13095     while (len > 0 && text[len - 1] == '\n') len--;
13096
13097     if (len == 0) return;
13098
13099     if (commentList[index] != NULL) {
13100         old = commentList[index];
13101         oldlen = strlen(old);
13102         while(commentList[index][oldlen-1] ==  '\n')
13103           commentList[index][--oldlen] = NULLCHAR;
13104         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13105         strcpy(commentList[index], old);
13106         free(old);
13107         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13108         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13109           if(addBraces) addBraces = FALSE; else { text++; len--; }
13110           while (*text == '\n') { text++; len--; }
13111           commentList[index][--oldlen] = NULLCHAR;
13112       }
13113         if(addBraces) strcat(commentList[index], "\n{\n");
13114         else          strcat(commentList[index], "\n");
13115         strcat(commentList[index], text);
13116         if(addBraces) strcat(commentList[index], "\n}\n");
13117         else          strcat(commentList[index], "\n");
13118     } else {
13119         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13120         if(addBraces)
13121              strcpy(commentList[index], "{\n");
13122         else commentList[index][0] = NULLCHAR;
13123         strcat(commentList[index], text);
13124         strcat(commentList[index], "\n");
13125         if(addBraces) strcat(commentList[index], "}\n");
13126     }
13127 }
13128
13129 static char * FindStr( char * text, char * sub_text )
13130 {
13131     char * result = strstr( text, sub_text );
13132
13133     if( result != NULL ) {
13134         result += strlen( sub_text );
13135     }
13136
13137     return result;
13138 }
13139
13140 /* [AS] Try to extract PV info from PGN comment */
13141 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13142 char *GetInfoFromComment( int index, char * text )
13143 {
13144     char * sep = text;
13145
13146     if( text != NULL && index > 0 ) {
13147         int score = 0;
13148         int depth = 0;
13149         int time = -1, sec = 0, deci;
13150         char * s_eval = FindStr( text, "[%eval " );
13151         char * s_emt = FindStr( text, "[%emt " );
13152
13153         if( s_eval != NULL || s_emt != NULL ) {
13154             /* New style */
13155             char delim;
13156
13157             if( s_eval != NULL ) {
13158                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13159                     return text;
13160                 }
13161
13162                 if( delim != ']' ) {
13163                     return text;
13164                 }
13165             }
13166
13167             if( s_emt != NULL ) {
13168             }
13169                 return text;
13170         }
13171         else {
13172             /* We expect something like: [+|-]nnn.nn/dd */
13173             int score_lo = 0;
13174
13175             if(*text != '{') return text; // [HGM] braces: must be normal comment
13176
13177             sep = strchr( text, '/' );
13178             if( sep == NULL || sep < (text+4) ) {
13179                 return text;
13180             }
13181
13182             time = -1; sec = -1; deci = -1;
13183             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13184                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13185                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13186                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13187                 return text;
13188             }
13189
13190             if( score_lo < 0 || score_lo >= 100 ) {
13191                 return text;
13192             }
13193
13194             if(sec >= 0) time = 600*time + 10*sec; else
13195             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13196
13197             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13198
13199             /* [HGM] PV time: now locate end of PV info */
13200             while( *++sep >= '0' && *sep <= '9'); // strip depth
13201             if(time >= 0)
13202             while( *++sep >= '0' && *sep <= '9'); // strip time
13203             if(sec >= 0)
13204             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13205             if(deci >= 0)
13206             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13207             while(*sep == ' ') sep++;
13208         }
13209
13210         if( depth <= 0 ) {
13211             return text;
13212         }
13213
13214         if( time < 0 ) {
13215             time = -1;
13216         }
13217
13218         pvInfoList[index-1].depth = depth;
13219         pvInfoList[index-1].score = score;
13220         pvInfoList[index-1].time  = 10*time; // centi-sec
13221         if(*sep == '}') *sep = 0; else *--sep = '{';
13222     }
13223     return sep;
13224 }
13225
13226 void
13227 SendToProgram(message, cps)
13228      char *message;
13229      ChessProgramState *cps;
13230 {
13231     int count, outCount, error;
13232     char buf[MSG_SIZ];
13233
13234     if (cps->pr == NULL) return;
13235     Attention(cps);
13236     
13237     if (appData.debugMode) {
13238         TimeMark now;
13239         GetTimeMark(&now);
13240         fprintf(debugFP, "%ld >%-6s: %s", 
13241                 SubtractTimeMarks(&now, &programStartTime),
13242                 cps->which, message);
13243     }
13244     
13245     count = strlen(message);
13246     outCount = OutputToProcess(cps->pr, message, count, &error);
13247     if (outCount < count && !exiting 
13248                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13249         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13250         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13251             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13252                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13253                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13254             } else {
13255                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13256             }
13257             gameInfo.resultDetails = StrSave(buf);
13258         }
13259         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13260     }
13261 }
13262
13263 void
13264 ReceiveFromProgram(isr, closure, message, count, error)
13265      InputSourceRef isr;
13266      VOIDSTAR closure;
13267      char *message;
13268      int count;
13269      int error;
13270 {
13271     char *end_str;
13272     char buf[MSG_SIZ];
13273     ChessProgramState *cps = (ChessProgramState *)closure;
13274
13275     if (isr != cps->isr) return; /* Killed intentionally */
13276     if (count <= 0) {
13277         if (count == 0) {
13278             sprintf(buf,
13279                     _("Error: %s chess program (%s) exited unexpectedly"),
13280                     cps->which, cps->program);
13281         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13282                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13283                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13284                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13285                 } else {
13286                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13287                 }
13288                 gameInfo.resultDetails = StrSave(buf);
13289             }
13290             RemoveInputSource(cps->isr);
13291             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13292         } else {
13293             sprintf(buf,
13294                     _("Error reading from %s chess program (%s)"),
13295                     cps->which, cps->program);
13296             RemoveInputSource(cps->isr);
13297
13298             /* [AS] Program is misbehaving badly... kill it */
13299             if( count == -2 ) {
13300                 DestroyChildProcess( cps->pr, 9 );
13301                 cps->pr = NoProc;
13302             }
13303
13304             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13305         }
13306         return;
13307     }
13308     
13309     if ((end_str = strchr(message, '\r')) != NULL)
13310       *end_str = NULLCHAR;
13311     if ((end_str = strchr(message, '\n')) != NULL)
13312       *end_str = NULLCHAR;
13313     
13314     if (appData.debugMode) {
13315         TimeMark now; int print = 1;
13316         char *quote = ""; char c; int i;
13317
13318         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13319                 char start = message[0];
13320                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13321                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13322                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13323                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13324                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13325                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13326                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13327                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13328                         { quote = "# "; print = (appData.engineComments == 2); }
13329                 message[0] = start; // restore original message
13330         }
13331         if(print) {
13332                 GetTimeMark(&now);
13333                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13334                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13335                         quote,
13336                         message);
13337         }
13338     }
13339
13340     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13341     if (appData.icsEngineAnalyze) {
13342         if (strstr(message, "whisper") != NULL ||
13343              strstr(message, "kibitz") != NULL || 
13344             strstr(message, "tellics") != NULL) return;
13345     }
13346
13347     HandleMachineMove(message, cps);
13348 }
13349
13350
13351 void
13352 SendTimeControl(cps, mps, tc, inc, sd, st)
13353      ChessProgramState *cps;
13354      int mps, inc, sd, st;
13355      long tc;
13356 {
13357     char buf[MSG_SIZ];
13358     int seconds;
13359
13360     if( timeControl_2 > 0 ) {
13361         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13362             tc = timeControl_2;
13363         }
13364     }
13365     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13366     inc /= cps->timeOdds;
13367     st  /= cps->timeOdds;
13368
13369     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13370
13371     if (st > 0) {
13372       /* Set exact time per move, normally using st command */
13373       if (cps->stKludge) {
13374         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13375         seconds = st % 60;
13376         if (seconds == 0) {
13377           sprintf(buf, "level 1 %d\n", st/60);
13378         } else {
13379           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13380         }
13381       } else {
13382         sprintf(buf, "st %d\n", st);
13383       }
13384     } else {
13385       /* Set conventional or incremental time control, using level command */
13386       if (seconds == 0) {
13387         /* Note old gnuchess bug -- minutes:seconds used to not work.
13388            Fixed in later versions, but still avoid :seconds
13389            when seconds is 0. */
13390         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13391       } else {
13392         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13393                 seconds, inc/1000);
13394       }
13395     }
13396     SendToProgram(buf, cps);
13397
13398     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13399     /* Orthogonally, limit search to given depth */
13400     if (sd > 0) {
13401       if (cps->sdKludge) {
13402         sprintf(buf, "depth\n%d\n", sd);
13403       } else {
13404         sprintf(buf, "sd %d\n", sd);
13405       }
13406       SendToProgram(buf, cps);
13407     }
13408
13409     if(cps->nps > 0) { /* [HGM] nps */
13410         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13411         else {
13412                 sprintf(buf, "nps %d\n", cps->nps);
13413               SendToProgram(buf, cps);
13414         }
13415     }
13416 }
13417
13418 ChessProgramState *WhitePlayer()
13419 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13420 {
13421     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13422        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13423         return &second;
13424     return &first;
13425 }
13426
13427 void
13428 SendTimeRemaining(cps, machineWhite)
13429      ChessProgramState *cps;
13430      int /*boolean*/ machineWhite;
13431 {
13432     char message[MSG_SIZ];
13433     long time, otime;
13434
13435     /* Note: this routine must be called when the clocks are stopped
13436        or when they have *just* been set or switched; otherwise
13437        it will be off by the time since the current tick started.
13438     */
13439     if (machineWhite) {
13440         time = whiteTimeRemaining / 10;
13441         otime = blackTimeRemaining / 10;
13442     } else {
13443         time = blackTimeRemaining / 10;
13444         otime = whiteTimeRemaining / 10;
13445     }
13446     /* [HGM] translate opponent's time by time-odds factor */
13447     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13448     if (appData.debugMode) {
13449         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13450     }
13451
13452     if (time <= 0) time = 1;
13453     if (otime <= 0) otime = 1;
13454     
13455     sprintf(message, "time %ld\n", time);
13456     SendToProgram(message, cps);
13457
13458     sprintf(message, "otim %ld\n", otime);
13459     SendToProgram(message, cps);
13460 }
13461
13462 int
13463 BoolFeature(p, name, loc, cps)
13464      char **p;
13465      char *name;
13466      int *loc;
13467      ChessProgramState *cps;
13468 {
13469   char buf[MSG_SIZ];
13470   int len = strlen(name);
13471   int val;
13472   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13473     (*p) += len + 1;
13474     sscanf(*p, "%d", &val);
13475     *loc = (val != 0);
13476     while (**p && **p != ' ') (*p)++;
13477     sprintf(buf, "accepted %s\n", name);
13478     SendToProgram(buf, cps);
13479     return TRUE;
13480   }
13481   return FALSE;
13482 }
13483
13484 int
13485 IntFeature(p, name, loc, cps)
13486      char **p;
13487      char *name;
13488      int *loc;
13489      ChessProgramState *cps;
13490 {
13491   char buf[MSG_SIZ];
13492   int len = strlen(name);
13493   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13494     (*p) += len + 1;
13495     sscanf(*p, "%d", loc);
13496     while (**p && **p != ' ') (*p)++;
13497     sprintf(buf, "accepted %s\n", name);
13498     SendToProgram(buf, cps);
13499     return TRUE;
13500   }
13501   return FALSE;
13502 }
13503
13504 int
13505 StringFeature(p, name, loc, cps)
13506      char **p;
13507      char *name;
13508      char loc[];
13509      ChessProgramState *cps;
13510 {
13511   char buf[MSG_SIZ];
13512   int len = strlen(name);
13513   if (strncmp((*p), name, len) == 0
13514       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13515     (*p) += len + 2;
13516     sscanf(*p, "%[^\"]", loc);
13517     while (**p && **p != '\"') (*p)++;
13518     if (**p == '\"') (*p)++;
13519     sprintf(buf, "accepted %s\n", name);
13520     SendToProgram(buf, cps);
13521     return TRUE;
13522   }
13523   return FALSE;
13524 }
13525
13526 int 
13527 ParseOption(Option *opt, ChessProgramState *cps)
13528 // [HGM] options: process the string that defines an engine option, and determine
13529 // name, type, default value, and allowed value range
13530 {
13531         char *p, *q, buf[MSG_SIZ];
13532         int n, min = (-1)<<31, max = 1<<31, def;
13533
13534         if(p = strstr(opt->name, " -spin ")) {
13535             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13536             if(max < min) max = min; // enforce consistency
13537             if(def < min) def = min;
13538             if(def > max) def = max;
13539             opt->value = def;
13540             opt->min = min;
13541             opt->max = max;
13542             opt->type = Spin;
13543         } else if((p = strstr(opt->name, " -slider "))) {
13544             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13545             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13546             if(max < min) max = min; // enforce consistency
13547             if(def < min) def = min;
13548             if(def > max) def = max;
13549             opt->value = def;
13550             opt->min = min;
13551             opt->max = max;
13552             opt->type = Spin; // Slider;
13553         } else if((p = strstr(opt->name, " -string "))) {
13554             opt->textValue = p+9;
13555             opt->type = TextBox;
13556         } else if((p = strstr(opt->name, " -file "))) {
13557             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13558             opt->textValue = p+7;
13559             opt->type = TextBox; // FileName;
13560         } else if((p = strstr(opt->name, " -path "))) {
13561             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13562             opt->textValue = p+7;
13563             opt->type = TextBox; // PathName;
13564         } else if(p = strstr(opt->name, " -check ")) {
13565             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13566             opt->value = (def != 0);
13567             opt->type = CheckBox;
13568         } else if(p = strstr(opt->name, " -combo ")) {
13569             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13570             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13571             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13572             opt->value = n = 0;
13573             while(q = StrStr(q, " /// ")) {
13574                 n++; *q = 0;    // count choices, and null-terminate each of them
13575                 q += 5;
13576                 if(*q == '*') { // remember default, which is marked with * prefix
13577                     q++;
13578                     opt->value = n;
13579                 }
13580                 cps->comboList[cps->comboCnt++] = q;
13581             }
13582             cps->comboList[cps->comboCnt++] = NULL;
13583             opt->max = n + 1;
13584             opt->type = ComboBox;
13585         } else if(p = strstr(opt->name, " -button")) {
13586             opt->type = Button;
13587         } else if(p = strstr(opt->name, " -save")) {
13588             opt->type = SaveButton;
13589         } else return FALSE;
13590         *p = 0; // terminate option name
13591         // now look if the command-line options define a setting for this engine option.
13592         if(cps->optionSettings && cps->optionSettings[0])
13593             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13594         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13595                 sprintf(buf, "option %s", p);
13596                 if(p = strstr(buf, ",")) *p = 0;
13597                 strcat(buf, "\n");
13598                 SendToProgram(buf, cps);
13599         }
13600         return TRUE;
13601 }
13602
13603 void
13604 FeatureDone(cps, val)
13605      ChessProgramState* cps;
13606      int val;
13607 {
13608   DelayedEventCallback cb = GetDelayedEvent();
13609   if ((cb == InitBackEnd3 && cps == &first) ||
13610       (cb == TwoMachinesEventIfReady && cps == &second)) {
13611     CancelDelayedEvent();
13612     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13613   }
13614   cps->initDone = val;
13615 }
13616
13617 /* Parse feature command from engine */
13618 void
13619 ParseFeatures(args, cps)
13620      char* args;
13621      ChessProgramState *cps;  
13622 {
13623   char *p = args;
13624   char *q;
13625   int val;
13626   char buf[MSG_SIZ];
13627
13628   for (;;) {
13629     while (*p == ' ') p++;
13630     if (*p == NULLCHAR) return;
13631
13632     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13633     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13634     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13635     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13636     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13637     if (BoolFeature(&p, "reuse", &val, cps)) {
13638       /* Engine can disable reuse, but can't enable it if user said no */
13639       if (!val) cps->reuse = FALSE;
13640       continue;
13641     }
13642     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13643     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13644       if (gameMode == TwoMachinesPlay) {
13645         DisplayTwoMachinesTitle();
13646       } else {
13647         DisplayTitle("");
13648       }
13649       continue;
13650     }
13651     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13652     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13653     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13654     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13655     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13656     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13657     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13658     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13659     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13660     if (IntFeature(&p, "done", &val, cps)) {
13661       FeatureDone(cps, val);
13662       continue;
13663     }
13664     /* Added by Tord: */
13665     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13666     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13667     /* End of additions by Tord */
13668
13669     /* [HGM] added features: */
13670     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13671     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13672     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13673     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13674     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13675     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13676     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13677         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13678             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13679             SendToProgram(buf, cps);
13680             continue;
13681         }
13682         if(cps->nrOptions >= MAX_OPTIONS) {
13683             cps->nrOptions--;
13684             sprintf(buf, "%s engine has too many options\n", cps->which);
13685             DisplayError(buf, 0);
13686         }
13687         continue;
13688     }
13689     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13690     /* End of additions by HGM */
13691
13692     /* unknown feature: complain and skip */
13693     q = p;
13694     while (*q && *q != '=') q++;
13695     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13696     SendToProgram(buf, cps);
13697     p = q;
13698     if (*p == '=') {
13699       p++;
13700       if (*p == '\"') {
13701         p++;
13702         while (*p && *p != '\"') p++;
13703         if (*p == '\"') p++;
13704       } else {
13705         while (*p && *p != ' ') p++;
13706       }
13707     }
13708   }
13709
13710 }
13711
13712 void
13713 PeriodicUpdatesEvent(newState)
13714      int newState;
13715 {
13716     if (newState == appData.periodicUpdates)
13717       return;
13718
13719     appData.periodicUpdates=newState;
13720
13721     /* Display type changes, so update it now */
13722 //    DisplayAnalysis();
13723
13724     /* Get the ball rolling again... */
13725     if (newState) {
13726         AnalysisPeriodicEvent(1);
13727         StartAnalysisClock();
13728     }
13729 }
13730
13731 void
13732 PonderNextMoveEvent(newState)
13733      int newState;
13734 {
13735     if (newState == appData.ponderNextMove) return;
13736     if (gameMode == EditPosition) EditPositionDone(TRUE);
13737     if (newState) {
13738         SendToProgram("hard\n", &first);
13739         if (gameMode == TwoMachinesPlay) {
13740             SendToProgram("hard\n", &second);
13741         }
13742     } else {
13743         SendToProgram("easy\n", &first);
13744         thinkOutput[0] = NULLCHAR;
13745         if (gameMode == TwoMachinesPlay) {
13746             SendToProgram("easy\n", &second);
13747         }
13748     }
13749     appData.ponderNextMove = newState;
13750 }
13751
13752 void
13753 NewSettingEvent(option, command, value)
13754      char *command;
13755      int option, value;
13756 {
13757     char buf[MSG_SIZ];
13758
13759     if (gameMode == EditPosition) EditPositionDone(TRUE);
13760     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13761     SendToProgram(buf, &first);
13762     if (gameMode == TwoMachinesPlay) {
13763         SendToProgram(buf, &second);
13764     }
13765 }
13766
13767 void
13768 ShowThinkingEvent()
13769 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13770 {
13771     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13772     int newState = appData.showThinking
13773         // [HGM] thinking: other features now need thinking output as well
13774         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13775     
13776     if (oldState == newState) return;
13777     oldState = newState;
13778     if (gameMode == EditPosition) EditPositionDone(TRUE);
13779     if (oldState) {
13780         SendToProgram("post\n", &first);
13781         if (gameMode == TwoMachinesPlay) {
13782             SendToProgram("post\n", &second);
13783         }
13784     } else {
13785         SendToProgram("nopost\n", &first);
13786         thinkOutput[0] = NULLCHAR;
13787         if (gameMode == TwoMachinesPlay) {
13788             SendToProgram("nopost\n", &second);
13789         }
13790     }
13791 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13792 }
13793
13794 void
13795 AskQuestionEvent(title, question, replyPrefix, which)
13796      char *title; char *question; char *replyPrefix; char *which;
13797 {
13798   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13799   if (pr == NoProc) return;
13800   AskQuestion(title, question, replyPrefix, pr);
13801 }
13802
13803 void
13804 DisplayMove(moveNumber)
13805      int moveNumber;
13806 {
13807     char message[MSG_SIZ];
13808     char res[MSG_SIZ];
13809     char cpThinkOutput[MSG_SIZ];
13810
13811     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13812     
13813     if (moveNumber == forwardMostMove - 1 || 
13814         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13815
13816         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13817
13818         if (strchr(cpThinkOutput, '\n')) {
13819             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13820         }
13821     } else {
13822         *cpThinkOutput = NULLCHAR;
13823     }
13824
13825     /* [AS] Hide thinking from human user */
13826     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13827         *cpThinkOutput = NULLCHAR;
13828         if( thinkOutput[0] != NULLCHAR ) {
13829             int i;
13830
13831             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13832                 cpThinkOutput[i] = '.';
13833             }
13834             cpThinkOutput[i] = NULLCHAR;
13835             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13836         }
13837     }
13838
13839     if (moveNumber == forwardMostMove - 1 &&
13840         gameInfo.resultDetails != NULL) {
13841         if (gameInfo.resultDetails[0] == NULLCHAR) {
13842             sprintf(res, " %s", PGNResult(gameInfo.result));
13843         } else {
13844             sprintf(res, " {%s} %s",
13845                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13846         }
13847     } else {
13848         res[0] = NULLCHAR;
13849     }
13850
13851     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13852         DisplayMessage(res, cpThinkOutput);
13853     } else {
13854         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13855                 WhiteOnMove(moveNumber) ? " " : ".. ",
13856                 parseList[moveNumber], res);
13857         DisplayMessage(message, cpThinkOutput);
13858     }
13859 }
13860
13861 void
13862 DisplayComment(moveNumber, text)
13863      int moveNumber;
13864      char *text;
13865 {
13866     char title[MSG_SIZ];
13867     char buf[8000]; // comment can be long!
13868     int score, depth;
13869     
13870     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13871       strcpy(title, "Comment");
13872     } else {
13873       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13874               WhiteOnMove(moveNumber) ? " " : ".. ",
13875               parseList[moveNumber]);
13876     }
13877     // [HGM] PV info: display PV info together with (or as) comment
13878     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13879       if(text == NULL) text = "";                                           
13880       score = pvInfoList[moveNumber].score;
13881       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13882               depth, (pvInfoList[moveNumber].time+50)/100, text);
13883       text = buf;
13884     }
13885     if (text != NULL && (appData.autoDisplayComment || commentUp))
13886         CommentPopUp(title, text);
13887 }
13888
13889 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13890  * might be busy thinking or pondering.  It can be omitted if your
13891  * gnuchess is configured to stop thinking immediately on any user
13892  * input.  However, that gnuchess feature depends on the FIONREAD
13893  * ioctl, which does not work properly on some flavors of Unix.
13894  */
13895 void
13896 Attention(cps)
13897      ChessProgramState *cps;
13898 {
13899 #if ATTENTION
13900     if (!cps->useSigint) return;
13901     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13902     switch (gameMode) {
13903       case MachinePlaysWhite:
13904       case MachinePlaysBlack:
13905       case TwoMachinesPlay:
13906       case IcsPlayingWhite:
13907       case IcsPlayingBlack:
13908       case AnalyzeMode:
13909       case AnalyzeFile:
13910         /* Skip if we know it isn't thinking */
13911         if (!cps->maybeThinking) return;
13912         if (appData.debugMode)
13913           fprintf(debugFP, "Interrupting %s\n", cps->which);
13914         InterruptChildProcess(cps->pr);
13915         cps->maybeThinking = FALSE;
13916         break;
13917       default:
13918         break;
13919     }
13920 #endif /*ATTENTION*/
13921 }
13922
13923 int
13924 CheckFlags()
13925 {
13926     if (whiteTimeRemaining <= 0) {
13927         if (!whiteFlag) {
13928             whiteFlag = TRUE;
13929             if (appData.icsActive) {
13930                 if (appData.autoCallFlag &&
13931                     gameMode == IcsPlayingBlack && !blackFlag) {
13932                   SendToICS(ics_prefix);
13933                   SendToICS("flag\n");
13934                 }
13935             } else {
13936                 if (blackFlag) {
13937                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13938                 } else {
13939                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13940                     if (appData.autoCallFlag) {
13941                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13942                         return TRUE;
13943                     }
13944                 }
13945             }
13946         }
13947     }
13948     if (blackTimeRemaining <= 0) {
13949         if (!blackFlag) {
13950             blackFlag = TRUE;
13951             if (appData.icsActive) {
13952                 if (appData.autoCallFlag &&
13953                     gameMode == IcsPlayingWhite && !whiteFlag) {
13954                   SendToICS(ics_prefix);
13955                   SendToICS("flag\n");
13956                 }
13957             } else {
13958                 if (whiteFlag) {
13959                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13960                 } else {
13961                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13962                     if (appData.autoCallFlag) {
13963                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13964                         return TRUE;
13965                     }
13966                 }
13967             }
13968         }
13969     }
13970     return FALSE;
13971 }
13972
13973 void
13974 CheckTimeControl()
13975 {
13976     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13977         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13978
13979     /*
13980      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13981      */
13982     if ( !WhiteOnMove(forwardMostMove) )
13983         /* White made time control */
13984         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13985         /* [HGM] time odds: correct new time quota for time odds! */
13986                                             / WhitePlayer()->timeOdds;
13987       else
13988         /* Black made time control */
13989         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13990                                             / WhitePlayer()->other->timeOdds;
13991 }
13992
13993 void
13994 DisplayBothClocks()
13995 {
13996     int wom = gameMode == EditPosition ?
13997       !blackPlaysFirst : WhiteOnMove(currentMove);
13998     DisplayWhiteClock(whiteTimeRemaining, wom);
13999     DisplayBlackClock(blackTimeRemaining, !wom);
14000 }
14001
14002
14003 /* Timekeeping seems to be a portability nightmare.  I think everyone
14004    has ftime(), but I'm really not sure, so I'm including some ifdefs
14005    to use other calls if you don't.  Clocks will be less accurate if
14006    you have neither ftime nor gettimeofday.
14007 */
14008
14009 /* VS 2008 requires the #include outside of the function */
14010 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14011 #include <sys/timeb.h>
14012 #endif
14013
14014 /* Get the current time as a TimeMark */
14015 void
14016 GetTimeMark(tm)
14017      TimeMark *tm;
14018 {
14019 #if HAVE_GETTIMEOFDAY
14020
14021     struct timeval timeVal;
14022     struct timezone timeZone;
14023
14024     gettimeofday(&timeVal, &timeZone);
14025     tm->sec = (long) timeVal.tv_sec; 
14026     tm->ms = (int) (timeVal.tv_usec / 1000L);
14027
14028 #else /*!HAVE_GETTIMEOFDAY*/
14029 #if HAVE_FTIME
14030
14031 // include <sys/timeb.h> / moved to just above start of function
14032     struct timeb timeB;
14033
14034     ftime(&timeB);
14035     tm->sec = (long) timeB.time;
14036     tm->ms = (int) timeB.millitm;
14037
14038 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14039     tm->sec = (long) time(NULL);
14040     tm->ms = 0;
14041 #endif
14042 #endif
14043 }
14044
14045 /* Return the difference in milliseconds between two
14046    time marks.  We assume the difference will fit in a long!
14047 */
14048 long
14049 SubtractTimeMarks(tm2, tm1)
14050      TimeMark *tm2, *tm1;
14051 {
14052     return 1000L*(tm2->sec - tm1->sec) +
14053            (long) (tm2->ms - tm1->ms);
14054 }
14055
14056
14057 /*
14058  * Code to manage the game clocks.
14059  *
14060  * In tournament play, black starts the clock and then white makes a move.
14061  * We give the human user a slight advantage if he is playing white---the
14062  * clocks don't run until he makes his first move, so it takes zero time.
14063  * Also, we don't account for network lag, so we could get out of sync
14064  * with GNU Chess's clock -- but then, referees are always right.  
14065  */
14066
14067 static TimeMark tickStartTM;
14068 static long intendedTickLength;
14069
14070 long
14071 NextTickLength(timeRemaining)
14072      long timeRemaining;
14073 {
14074     long nominalTickLength, nextTickLength;
14075
14076     if (timeRemaining > 0L && timeRemaining <= 10000L)
14077       nominalTickLength = 100L;
14078     else
14079       nominalTickLength = 1000L;
14080     nextTickLength = timeRemaining % nominalTickLength;
14081     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14082
14083     return nextTickLength;
14084 }
14085
14086 /* Adjust clock one minute up or down */
14087 void
14088 AdjustClock(Boolean which, int dir)
14089 {
14090     if(which) blackTimeRemaining += 60000*dir;
14091     else      whiteTimeRemaining += 60000*dir;
14092     DisplayBothClocks();
14093 }
14094
14095 /* Stop clocks and reset to a fresh time control */
14096 void
14097 ResetClocks() 
14098 {
14099     (void) StopClockTimer();
14100     if (appData.icsActive) {
14101         whiteTimeRemaining = blackTimeRemaining = 0;
14102     } else if (searchTime) {
14103         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14104         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14105     } else { /* [HGM] correct new time quote for time odds */
14106         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14107         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14108     }
14109     if (whiteFlag || blackFlag) {
14110         DisplayTitle("");
14111         whiteFlag = blackFlag = FALSE;
14112     }
14113     DisplayBothClocks();
14114 }
14115
14116 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14117
14118 /* Decrement running clock by amount of time that has passed */
14119 void
14120 DecrementClocks()
14121 {
14122     long timeRemaining;
14123     long lastTickLength, fudge;
14124     TimeMark now;
14125
14126     if (!appData.clockMode) return;
14127     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14128         
14129     GetTimeMark(&now);
14130
14131     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14132
14133     /* Fudge if we woke up a little too soon */
14134     fudge = intendedTickLength - lastTickLength;
14135     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14136
14137     if (WhiteOnMove(forwardMostMove)) {
14138         if(whiteNPS >= 0) lastTickLength = 0;
14139         timeRemaining = whiteTimeRemaining -= lastTickLength;
14140         DisplayWhiteClock(whiteTimeRemaining - fudge,
14141                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14142     } else {
14143         if(blackNPS >= 0) lastTickLength = 0;
14144         timeRemaining = blackTimeRemaining -= lastTickLength;
14145         DisplayBlackClock(blackTimeRemaining - fudge,
14146                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14147     }
14148
14149     if (CheckFlags()) return;
14150         
14151     tickStartTM = now;
14152     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14153     StartClockTimer(intendedTickLength);
14154
14155     /* if the time remaining has fallen below the alarm threshold, sound the
14156      * alarm. if the alarm has sounded and (due to a takeback or time control
14157      * with increment) the time remaining has increased to a level above the
14158      * threshold, reset the alarm so it can sound again. 
14159      */
14160     
14161     if (appData.icsActive && appData.icsAlarm) {
14162
14163         /* make sure we are dealing with the user's clock */
14164         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14165                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14166            )) return;
14167
14168         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14169             alarmSounded = FALSE;
14170         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14171             PlayAlarmSound();
14172             alarmSounded = TRUE;
14173         }
14174     }
14175 }
14176
14177
14178 /* A player has just moved, so stop the previously running
14179    clock and (if in clock mode) start the other one.
14180    We redisplay both clocks in case we're in ICS mode, because
14181    ICS gives us an update to both clocks after every move.
14182    Note that this routine is called *after* forwardMostMove
14183    is updated, so the last fractional tick must be subtracted
14184    from the color that is *not* on move now.
14185 */
14186 void
14187 SwitchClocks(int newMoveNr)
14188 {
14189     long lastTickLength;
14190     TimeMark now;
14191     int flagged = FALSE;
14192
14193     GetTimeMark(&now);
14194
14195     if (StopClockTimer() && appData.clockMode) {
14196         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14197         if (!WhiteOnMove(forwardMostMove)) {
14198             if(blackNPS >= 0) lastTickLength = 0;
14199             blackTimeRemaining -= lastTickLength;
14200            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14201 //         if(pvInfoList[forwardMostMove-1].time == -1)
14202                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14203                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14204         } else {
14205            if(whiteNPS >= 0) lastTickLength = 0;
14206            whiteTimeRemaining -= lastTickLength;
14207            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14208 //         if(pvInfoList[forwardMostMove-1].time == -1)
14209                  pvInfoList[forwardMostMove-1].time = 
14210                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14211         }
14212         flagged = CheckFlags();
14213     }
14214     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14215     CheckTimeControl();
14216
14217     if (flagged || !appData.clockMode) return;
14218
14219     switch (gameMode) {
14220       case MachinePlaysBlack:
14221       case MachinePlaysWhite:
14222       case BeginningOfGame:
14223         if (pausing) return;
14224         break;
14225
14226       case EditGame:
14227       case PlayFromGameFile:
14228       case IcsExamining:
14229         return;
14230
14231       default:
14232         break;
14233     }
14234
14235     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14236         if(WhiteOnMove(forwardMostMove))
14237              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14238         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14239     }
14240
14241     tickStartTM = now;
14242     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14243       whiteTimeRemaining : blackTimeRemaining);
14244     StartClockTimer(intendedTickLength);
14245 }
14246         
14247
14248 /* Stop both clocks */
14249 void
14250 StopClocks()
14251 {       
14252     long lastTickLength;
14253     TimeMark now;
14254
14255     if (!StopClockTimer()) return;
14256     if (!appData.clockMode) return;
14257
14258     GetTimeMark(&now);
14259
14260     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14261     if (WhiteOnMove(forwardMostMove)) {
14262         if(whiteNPS >= 0) lastTickLength = 0;
14263         whiteTimeRemaining -= lastTickLength;
14264         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14265     } else {
14266         if(blackNPS >= 0) lastTickLength = 0;
14267         blackTimeRemaining -= lastTickLength;
14268         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14269     }
14270     CheckFlags();
14271 }
14272         
14273 /* Start clock of player on move.  Time may have been reset, so
14274    if clock is already running, stop and restart it. */
14275 void
14276 StartClocks()
14277 {
14278     (void) StopClockTimer(); /* in case it was running already */
14279     DisplayBothClocks();
14280     if (CheckFlags()) return;
14281
14282     if (!appData.clockMode) return;
14283     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14284
14285     GetTimeMark(&tickStartTM);
14286     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14287       whiteTimeRemaining : blackTimeRemaining);
14288
14289    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14290     whiteNPS = blackNPS = -1; 
14291     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14292        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14293         whiteNPS = first.nps;
14294     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14295        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14296         blackNPS = first.nps;
14297     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14298         whiteNPS = second.nps;
14299     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14300         blackNPS = second.nps;
14301     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14302
14303     StartClockTimer(intendedTickLength);
14304 }
14305
14306 char *
14307 TimeString(ms)
14308      long ms;
14309 {
14310     long second, minute, hour, day;
14311     char *sign = "";
14312     static char buf[32];
14313     
14314     if (ms > 0 && ms <= 9900) {
14315       /* convert milliseconds to tenths, rounding up */
14316       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14317
14318       sprintf(buf, " %03.1f ", tenths/10.0);
14319       return buf;
14320     }
14321
14322     /* convert milliseconds to seconds, rounding up */
14323     /* use floating point to avoid strangeness of integer division
14324        with negative dividends on many machines */
14325     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14326
14327     if (second < 0) {
14328         sign = "-";
14329         second = -second;
14330     }
14331     
14332     day = second / (60 * 60 * 24);
14333     second = second % (60 * 60 * 24);
14334     hour = second / (60 * 60);
14335     second = second % (60 * 60);
14336     minute = second / 60;
14337     second = second % 60;
14338     
14339     if (day > 0)
14340       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14341               sign, day, hour, minute, second);
14342     else if (hour > 0)
14343       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14344     else
14345       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14346     
14347     return buf;
14348 }
14349
14350
14351 /*
14352  * This is necessary because some C libraries aren't ANSI C compliant yet.
14353  */
14354 char *
14355 StrStr(string, match)
14356      char *string, *match;
14357 {
14358     int i, length;
14359     
14360     length = strlen(match);
14361     
14362     for (i = strlen(string) - length; i >= 0; i--, string++)
14363       if (!strncmp(match, string, length))
14364         return string;
14365     
14366     return NULL;
14367 }
14368
14369 char *
14370 StrCaseStr(string, match)
14371      char *string, *match;
14372 {
14373     int i, j, length;
14374     
14375     length = strlen(match);
14376     
14377     for (i = strlen(string) - length; i >= 0; i--, string++) {
14378         for (j = 0; j < length; j++) {
14379             if (ToLower(match[j]) != ToLower(string[j]))
14380               break;
14381         }
14382         if (j == length) return string;
14383     }
14384
14385     return NULL;
14386 }
14387
14388 #ifndef _amigados
14389 int
14390 StrCaseCmp(s1, s2)
14391      char *s1, *s2;
14392 {
14393     char c1, c2;
14394     
14395     for (;;) {
14396         c1 = ToLower(*s1++);
14397         c2 = ToLower(*s2++);
14398         if (c1 > c2) return 1;
14399         if (c1 < c2) return -1;
14400         if (c1 == NULLCHAR) return 0;
14401     }
14402 }
14403
14404
14405 int
14406 ToLower(c)
14407      int c;
14408 {
14409     return isupper(c) ? tolower(c) : c;
14410 }
14411
14412
14413 int
14414 ToUpper(c)
14415      int c;
14416 {
14417     return islower(c) ? toupper(c) : c;
14418 }
14419 #endif /* !_amigados    */
14420
14421 char *
14422 StrSave(s)
14423      char *s;
14424 {
14425     char *ret;
14426
14427     if ((ret = (char *) malloc(strlen(s) + 1))) {
14428         strcpy(ret, s);
14429     }
14430     return ret;
14431 }
14432
14433 char *
14434 StrSavePtr(s, savePtr)
14435      char *s, **savePtr;
14436 {
14437     if (*savePtr) {
14438         free(*savePtr);
14439     }
14440     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14441         strcpy(*savePtr, s);
14442     }
14443     return(*savePtr);
14444 }
14445
14446 char *
14447 PGNDate()
14448 {
14449     time_t clock;
14450     struct tm *tm;
14451     char buf[MSG_SIZ];
14452
14453     clock = time((time_t *)NULL);
14454     tm = localtime(&clock);
14455     sprintf(buf, "%04d.%02d.%02d",
14456             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14457     return StrSave(buf);
14458 }
14459
14460
14461 char *
14462 PositionToFEN(move, overrideCastling)
14463      int move;
14464      char *overrideCastling;
14465 {
14466     int i, j, fromX, fromY, toX, toY;
14467     int whiteToPlay;
14468     char buf[128];
14469     char *p, *q;
14470     int emptycount;
14471     ChessSquare piece;
14472
14473     whiteToPlay = (gameMode == EditPosition) ?
14474       !blackPlaysFirst : (move % 2 == 0);
14475     p = buf;
14476
14477     /* Piece placement data */
14478     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14479         emptycount = 0;
14480         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14481             if (boards[move][i][j] == EmptySquare) {
14482                 emptycount++;
14483             } else { ChessSquare piece = boards[move][i][j];
14484                 if (emptycount > 0) {
14485                     if(emptycount<10) /* [HGM] can be >= 10 */
14486                         *p++ = '0' + emptycount;
14487                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14488                     emptycount = 0;
14489                 }
14490                 if(PieceToChar(piece) == '+') {
14491                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14492                     *p++ = '+';
14493                     piece = (ChessSquare)(DEMOTED piece);
14494                 } 
14495                 *p++ = PieceToChar(piece);
14496                 if(p[-1] == '~') {
14497                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14498                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14499                     *p++ = '~';
14500                 }
14501             }
14502         }
14503         if (emptycount > 0) {
14504             if(emptycount<10) /* [HGM] can be >= 10 */
14505                 *p++ = '0' + emptycount;
14506             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14507             emptycount = 0;
14508         }
14509         *p++ = '/';
14510     }
14511     *(p - 1) = ' ';
14512
14513     /* [HGM] print Crazyhouse or Shogi holdings */
14514     if( gameInfo.holdingsWidth ) {
14515         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14516         q = p;
14517         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14518             piece = boards[move][i][BOARD_WIDTH-1];
14519             if( piece != EmptySquare )
14520               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14521                   *p++ = PieceToChar(piece);
14522         }
14523         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14524             piece = boards[move][BOARD_HEIGHT-i-1][0];
14525             if( piece != EmptySquare )
14526               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14527                   *p++ = PieceToChar(piece);
14528         }
14529
14530         if( q == p ) *p++ = '-';
14531         *p++ = ']';
14532         *p++ = ' ';
14533     }
14534
14535     /* Active color */
14536     *p++ = whiteToPlay ? 'w' : 'b';
14537     *p++ = ' ';
14538
14539   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14540     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14541   } else {
14542   if(nrCastlingRights) {
14543      q = p;
14544      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14545        /* [HGM] write directly from rights */
14546            if(boards[move][CASTLING][2] != NoRights &&
14547               boards[move][CASTLING][0] != NoRights   )
14548                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14549            if(boards[move][CASTLING][2] != NoRights &&
14550               boards[move][CASTLING][1] != NoRights   )
14551                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14552            if(boards[move][CASTLING][5] != NoRights &&
14553               boards[move][CASTLING][3] != NoRights   )
14554                 *p++ = boards[move][CASTLING][3] + AAA;
14555            if(boards[move][CASTLING][5] != NoRights &&
14556               boards[move][CASTLING][4] != NoRights   )
14557                 *p++ = boards[move][CASTLING][4] + AAA;
14558      } else {
14559
14560         /* [HGM] write true castling rights */
14561         if( nrCastlingRights == 6 ) {
14562             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14563                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14564             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14565                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14566             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14567                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14568             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14569                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14570         }
14571      }
14572      if (q == p) *p++ = '-'; /* No castling rights */
14573      *p++ = ' ';
14574   }
14575
14576   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14577      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14578     /* En passant target square */
14579     if (move > backwardMostMove) {
14580         fromX = moveList[move - 1][0] - AAA;
14581         fromY = moveList[move - 1][1] - ONE;
14582         toX = moveList[move - 1][2] - AAA;
14583         toY = moveList[move - 1][3] - ONE;
14584         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14585             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14586             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14587             fromX == toX) {
14588             /* 2-square pawn move just happened */
14589             *p++ = toX + AAA;
14590             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14591         } else {
14592             *p++ = '-';
14593         }
14594     } else if(move == backwardMostMove) {
14595         // [HGM] perhaps we should always do it like this, and forget the above?
14596         if((signed char)boards[move][EP_STATUS] >= 0) {
14597             *p++ = boards[move][EP_STATUS] + AAA;
14598             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14599         } else {
14600             *p++ = '-';
14601         }
14602     } else {
14603         *p++ = '-';
14604     }
14605     *p++ = ' ';
14606   }
14607   }
14608
14609     /* [HGM] find reversible plies */
14610     {   int i = 0, j=move;
14611
14612         if (appData.debugMode) { int k;
14613             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14614             for(k=backwardMostMove; k<=forwardMostMove; k++)
14615                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14616
14617         }
14618
14619         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14620         if( j == backwardMostMove ) i += initialRulePlies;
14621         sprintf(p, "%d ", i);
14622         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14623     }
14624     /* Fullmove number */
14625     sprintf(p, "%d", (move / 2) + 1);
14626     
14627     return StrSave(buf);
14628 }
14629
14630 Boolean
14631 ParseFEN(board, blackPlaysFirst, fen)
14632     Board board;
14633      int *blackPlaysFirst;
14634      char *fen;
14635 {
14636     int i, j;
14637     char *p;
14638     int emptycount;
14639     ChessSquare piece;
14640
14641     p = fen;
14642
14643     /* [HGM] by default clear Crazyhouse holdings, if present */
14644     if(gameInfo.holdingsWidth) {
14645        for(i=0; i<BOARD_HEIGHT; i++) {
14646            board[i][0]             = EmptySquare; /* black holdings */
14647            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14648            board[i][1]             = (ChessSquare) 0; /* black counts */
14649            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14650        }
14651     }
14652
14653     /* Piece placement data */
14654     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14655         j = 0;
14656         for (;;) {
14657             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14658                 if (*p == '/') p++;
14659                 emptycount = gameInfo.boardWidth - j;
14660                 while (emptycount--)
14661                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14662                 break;
14663 #if(BOARD_FILES >= 10)
14664             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14665                 p++; emptycount=10;
14666                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14667                 while (emptycount--)
14668                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14669 #endif
14670             } else if (isdigit(*p)) {
14671                 emptycount = *p++ - '0';
14672                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14673                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14674                 while (emptycount--)
14675                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14676             } else if (*p == '+' || isalpha(*p)) {
14677                 if (j >= gameInfo.boardWidth) return FALSE;
14678                 if(*p=='+') {
14679                     piece = CharToPiece(*++p);
14680                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14681                     piece = (ChessSquare) (PROMOTED piece ); p++;
14682                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14683                 } else piece = CharToPiece(*p++);
14684
14685                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14686                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14687                     piece = (ChessSquare) (PROMOTED piece);
14688                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14689                     p++;
14690                 }
14691                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14692             } else {
14693                 return FALSE;
14694             }
14695         }
14696     }
14697     while (*p == '/' || *p == ' ') p++;
14698
14699     /* [HGM] look for Crazyhouse holdings here */
14700     while(*p==' ') p++;
14701     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14702         if(*p == '[') p++;
14703         if(*p == '-' ) *p++; /* empty holdings */ else {
14704             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14705             /* if we would allow FEN reading to set board size, we would   */
14706             /* have to add holdings and shift the board read so far here   */
14707             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14708                 *p++;
14709                 if((int) piece >= (int) BlackPawn ) {
14710                     i = (int)piece - (int)BlackPawn;
14711                     i = PieceToNumber((ChessSquare)i);
14712                     if( i >= gameInfo.holdingsSize ) return FALSE;
14713                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14714                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14715                 } else {
14716                     i = (int)piece - (int)WhitePawn;
14717                     i = PieceToNumber((ChessSquare)i);
14718                     if( i >= gameInfo.holdingsSize ) return FALSE;
14719                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14720                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14721                 }
14722             }
14723         }
14724         if(*p == ']') *p++;
14725     }
14726
14727     while(*p == ' ') p++;
14728
14729     /* Active color */
14730     switch (*p++) {
14731       case 'w':
14732         *blackPlaysFirst = FALSE;
14733         break;
14734       case 'b': 
14735         *blackPlaysFirst = TRUE;
14736         break;
14737       default:
14738         return FALSE;
14739     }
14740
14741     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14742     /* return the extra info in global variiables             */
14743
14744     /* set defaults in case FEN is incomplete */
14745     board[EP_STATUS] = EP_UNKNOWN;
14746     for(i=0; i<nrCastlingRights; i++ ) {
14747         board[CASTLING][i] =
14748             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14749     }   /* assume possible unless obviously impossible */
14750     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14751     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14752     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14753                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14754     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14755     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14756     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14757                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14758     FENrulePlies = 0;
14759
14760     while(*p==' ') p++;
14761     if(nrCastlingRights) {
14762       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14763           /* castling indicator present, so default becomes no castlings */
14764           for(i=0; i<nrCastlingRights; i++ ) {
14765                  board[CASTLING][i] = NoRights;
14766           }
14767       }
14768       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14769              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14770              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14771              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14772         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14773
14774         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14775             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14776             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14777         }
14778         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14779             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14780         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14781                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14782         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14783                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14784         switch(c) {
14785           case'K':
14786               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14787               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14788               board[CASTLING][2] = whiteKingFile;
14789               break;
14790           case'Q':
14791               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14792               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14793               board[CASTLING][2] = whiteKingFile;
14794               break;
14795           case'k':
14796               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14797               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14798               board[CASTLING][5] = blackKingFile;
14799               break;
14800           case'q':
14801               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14802               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14803               board[CASTLING][5] = blackKingFile;
14804           case '-':
14805               break;
14806           default: /* FRC castlings */
14807               if(c >= 'a') { /* black rights */
14808                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14809                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14810                   if(i == BOARD_RGHT) break;
14811                   board[CASTLING][5] = i;
14812                   c -= AAA;
14813                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14814                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14815                   if(c > i)
14816                       board[CASTLING][3] = c;
14817                   else
14818                       board[CASTLING][4] = c;
14819               } else { /* white rights */
14820                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14821                     if(board[0][i] == WhiteKing) break;
14822                   if(i == BOARD_RGHT) break;
14823                   board[CASTLING][2] = i;
14824                   c -= AAA - 'a' + 'A';
14825                   if(board[0][c] >= WhiteKing) break;
14826                   if(c > i)
14827                       board[CASTLING][0] = c;
14828                   else
14829                       board[CASTLING][1] = c;
14830               }
14831         }
14832       }
14833       for(i=0; i<nrCastlingRights; i++)
14834         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14835     if (appData.debugMode) {
14836         fprintf(debugFP, "FEN castling rights:");
14837         for(i=0; i<nrCastlingRights; i++)
14838         fprintf(debugFP, " %d", board[CASTLING][i]);
14839         fprintf(debugFP, "\n");
14840     }
14841
14842       while(*p==' ') p++;
14843     }
14844
14845     /* read e.p. field in games that know e.p. capture */
14846     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14847        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14848       if(*p=='-') {
14849         p++; board[EP_STATUS] = EP_NONE;
14850       } else {
14851          char c = *p++ - AAA;
14852
14853          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14854          if(*p >= '0' && *p <='9') *p++;
14855          board[EP_STATUS] = c;
14856       }
14857     }
14858
14859
14860     if(sscanf(p, "%d", &i) == 1) {
14861         FENrulePlies = i; /* 50-move ply counter */
14862         /* (The move number is still ignored)    */
14863     }
14864
14865     return TRUE;
14866 }
14867       
14868 void
14869 EditPositionPasteFEN(char *fen)
14870 {
14871   if (fen != NULL) {
14872     Board initial_position;
14873
14874     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14875       DisplayError(_("Bad FEN position in clipboard"), 0);
14876       return ;
14877     } else {
14878       int savedBlackPlaysFirst = blackPlaysFirst;
14879       EditPositionEvent();
14880       blackPlaysFirst = savedBlackPlaysFirst;
14881       CopyBoard(boards[0], initial_position);
14882       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14883       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14884       DisplayBothClocks();
14885       DrawPosition(FALSE, boards[currentMove]);
14886     }
14887   }
14888 }
14889
14890 static char cseq[12] = "\\   ";
14891
14892 Boolean set_cont_sequence(char *new_seq)
14893 {
14894     int len;
14895     Boolean ret;
14896
14897     // handle bad attempts to set the sequence
14898         if (!new_seq)
14899                 return 0; // acceptable error - no debug
14900
14901     len = strlen(new_seq);
14902     ret = (len > 0) && (len < sizeof(cseq));
14903     if (ret)
14904         strcpy(cseq, new_seq);
14905     else if (appData.debugMode)
14906         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14907     return ret;
14908 }
14909
14910 /*
14911     reformat a source message so words don't cross the width boundary.  internal
14912     newlines are not removed.  returns the wrapped size (no null character unless
14913     included in source message).  If dest is NULL, only calculate the size required
14914     for the dest buffer.  lp argument indicats line position upon entry, and it's
14915     passed back upon exit.
14916 */
14917 int wrap(char *dest, char *src, int count, int width, int *lp)
14918 {
14919     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14920
14921     cseq_len = strlen(cseq);
14922     old_line = line = *lp;
14923     ansi = len = clen = 0;
14924
14925     for (i=0; i < count; i++)
14926     {
14927         if (src[i] == '\033')
14928             ansi = 1;
14929
14930         // if we hit the width, back up
14931         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14932         {
14933             // store i & len in case the word is too long
14934             old_i = i, old_len = len;
14935
14936             // find the end of the last word
14937             while (i && src[i] != ' ' && src[i] != '\n')
14938             {
14939                 i--;
14940                 len--;
14941             }
14942
14943             // word too long?  restore i & len before splitting it
14944             if ((old_i-i+clen) >= width)
14945             {
14946                 i = old_i;
14947                 len = old_len;
14948             }
14949
14950             // extra space?
14951             if (i && src[i-1] == ' ')
14952                 len--;
14953
14954             if (src[i] != ' ' && src[i] != '\n')
14955             {
14956                 i--;
14957                 if (len)
14958                     len--;
14959             }
14960
14961             // now append the newline and continuation sequence
14962             if (dest)
14963                 dest[len] = '\n';
14964             len++;
14965             if (dest)
14966                 strncpy(dest+len, cseq, cseq_len);
14967             len += cseq_len;
14968             line = cseq_len;
14969             clen = cseq_len;
14970             continue;
14971         }
14972
14973         if (dest)
14974             dest[len] = src[i];
14975         len++;
14976         if (!ansi)
14977             line++;
14978         if (src[i] == '\n')
14979             line = 0;
14980         if (src[i] == 'm')
14981             ansi = 0;
14982     }
14983     if (dest && appData.debugMode)
14984     {
14985         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14986             count, width, line, len, *lp);
14987         show_bytes(debugFP, src, count);
14988         fprintf(debugFP, "\ndest: ");
14989         show_bytes(debugFP, dest, len);
14990         fprintf(debugFP, "\n");
14991     }
14992     *lp = dest ? line : old_line;
14993
14994     return len;
14995 }
14996
14997 // [HGM] vari: routines for shelving variations
14998
14999 void 
15000 PushTail(int firstMove, int lastMove)
15001 {
15002         int i, j, nrMoves = lastMove - firstMove;
15003
15004         if(appData.icsActive) { // only in local mode
15005                 forwardMostMove = currentMove; // mimic old ICS behavior
15006                 return;
15007         }
15008         if(storedGames >= MAX_VARIATIONS-1) return;
15009
15010         // push current tail of game on stack
15011         savedResult[storedGames] = gameInfo.result;
15012         savedDetails[storedGames] = gameInfo.resultDetails;
15013         gameInfo.resultDetails = NULL;
15014         savedFirst[storedGames] = firstMove;
15015         savedLast [storedGames] = lastMove;
15016         savedFramePtr[storedGames] = framePtr;
15017         framePtr -= nrMoves; // reserve space for the boards
15018         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15019             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15020             for(j=0; j<MOVE_LEN; j++)
15021                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15022             for(j=0; j<2*MOVE_LEN; j++)
15023                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15024             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15025             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15026             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15027             pvInfoList[firstMove+i-1].depth = 0;
15028             commentList[framePtr+i] = commentList[firstMove+i];
15029             commentList[firstMove+i] = NULL;
15030         }
15031
15032         storedGames++;
15033         forwardMostMove = currentMove; // truncte game so we can start variation
15034         if(storedGames == 1) GreyRevert(FALSE);
15035 }
15036
15037 Boolean
15038 PopTail(Boolean annotate)
15039 {
15040         int i, j, nrMoves;
15041         char buf[8000], moveBuf[20];
15042
15043         if(appData.icsActive) return FALSE; // only in local mode
15044         if(!storedGames) return FALSE; // sanity
15045
15046         storedGames--;
15047         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15048         nrMoves = savedLast[storedGames] - currentMove;
15049         if(annotate) {
15050                 int cnt = 10;
15051                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15052                 else strcpy(buf, "(");
15053                 for(i=currentMove; i<forwardMostMove; i++) {
15054                         if(WhiteOnMove(i))
15055                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15056                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15057                         strcat(buf, moveBuf);
15058                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15059                 }
15060                 strcat(buf, ")");
15061         }
15062         for(i=1; i<nrMoves; i++) { // copy last variation back
15063             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15064             for(j=0; j<MOVE_LEN; j++)
15065                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15066             for(j=0; j<2*MOVE_LEN; j++)
15067                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15068             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15069             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15070             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15071             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15072             commentList[currentMove+i] = commentList[framePtr+i];
15073             commentList[framePtr+i] = NULL;
15074         }
15075         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15076         framePtr = savedFramePtr[storedGames];
15077         gameInfo.result = savedResult[storedGames];
15078         if(gameInfo.resultDetails != NULL) {
15079             free(gameInfo.resultDetails);
15080       }
15081         gameInfo.resultDetails = savedDetails[storedGames];
15082         forwardMostMove = currentMove + nrMoves;
15083         if(storedGames == 0) GreyRevert(TRUE);
15084         return TRUE;
15085 }
15086
15087 void 
15088 CleanupTail()
15089 {       // remove all shelved variations
15090         int i;
15091         for(i=0; i<storedGames; i++) {
15092             if(savedDetails[i])
15093                 free(savedDetails[i]);
15094             savedDetails[i] = NULL;
15095         }
15096         for(i=framePtr; i<MAX_MOVES; i++) {
15097                 if(commentList[i]) free(commentList[i]);
15098                 commentList[i] = NULL;
15099         }
15100         framePtr = MAX_MOVES-1;
15101         storedGames = 0;
15102 }