Allow arrow keys in WB Chat Box to access command history
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2744                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2745                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2746                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2747                 int p;
2748                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2749                 chattingPartner = -1;
2750
2751                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2752                 for(p=0; p<MAX_CHAT; p++) {
2753                     if(channel == atoi(chatPartner[p])) {
2754                     talker[0] = '['; strcat(talker, "] ");
2755                     chattingPartner = p; break;
2756                     }
2757                 } else
2758                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2759                 for(p=0; p<MAX_CHAT; p++) {
2760                     if(!strcmp("WHISPER", chatPartner[p])) {
2761                         talker[0] = '['; strcat(talker, "] ");
2762                         chattingPartner = p; break;
2763                     }
2764                 }
2765                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2766                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2767                     talker[0] = 0;
2768                     chattingPartner = p; break;
2769                 }
2770                 if(chattingPartner<0) i = oldi; else {
2771                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2772                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2773                     started = STARTED_COMMENT;
2774                     parse_pos = 0; parse[0] = NULLCHAR;
2775                     savingComment = 3 + chattingPartner; // counts as TRUE
2776                     suppressKibitz = TRUE;
2777                     continue;
2778                 }
2779             } // [HGM] chat: end of patch
2780
2781             if (appData.zippyTalk || appData.zippyPlay) {
2782                 /* [DM] Backup address for color zippy lines */
2783                 backup = i;
2784 #if ZIPPY
2785        #ifdef WIN32
2786                if (loggedOn == TRUE)
2787                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2788                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2789        #else
2790                 if (ZippyControl(buf, &i) ||
2791                     ZippyConverse(buf, &i) ||
2792                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2793                       loggedOn = TRUE;
2794                       if (!appData.colorize) continue;
2795                 }
2796        #endif
2797 #endif
2798             } // [DM] 'else { ' deleted
2799                 if (
2800                     /* Regular tells and says */
2801                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2802                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2803                     looking_at(buf, &i, "* says: ") ||
2804                     /* Don't color "message" or "messages" output */
2805                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2806                     looking_at(buf, &i, "*. * at *:*: ") ||
2807                     looking_at(buf, &i, "--* (*:*): ") ||
2808                     /* Message notifications (same color as tells) */
2809                     looking_at(buf, &i, "* has left a message ") ||
2810                     looking_at(buf, &i, "* just sent you a message:\n") ||
2811                     /* Whispers and kibitzes */
2812                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2813                     looking_at(buf, &i, "* kibitzes: ") ||
2814                     /* Channel tells */
2815                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2816
2817                   if (tkind == 1 && strchr(star_match[0], ':')) {
2818                       /* Avoid "tells you:" spoofs in channels */
2819                      tkind = 3;
2820                   }
2821                   if (star_match[0][0] == NULLCHAR ||
2822                       strchr(star_match[0], ' ') ||
2823                       (tkind == 3 && strchr(star_match[1], ' '))) {
2824                     /* Reject bogus matches */
2825                     i = oldi;
2826                   } else {
2827                     if (appData.colorize) {
2828                       if (oldi > next_out) {
2829                         SendToPlayer(&buf[next_out], oldi - next_out);
2830                         next_out = oldi;
2831                       }
2832                       switch (tkind) {
2833                       case 1:
2834                         Colorize(ColorTell, FALSE);
2835                         curColor = ColorTell;
2836                         break;
2837                       case 2:
2838                         Colorize(ColorKibitz, FALSE);
2839                         curColor = ColorKibitz;
2840                         break;
2841                       case 3:
2842                         p = strrchr(star_match[1], '(');
2843                         if (p == NULL) {
2844                           p = star_match[1];
2845                         } else {
2846                           p++;
2847                         }
2848                         if (atoi(p) == 1) {
2849                           Colorize(ColorChannel1, FALSE);
2850                           curColor = ColorChannel1;
2851                         } else {
2852                           Colorize(ColorChannel, FALSE);
2853                           curColor = ColorChannel;
2854                         }
2855                         break;
2856                       case 5:
2857                         curColor = ColorNormal;
2858                         break;
2859                       }
2860                     }
2861                     if (started == STARTED_NONE && appData.autoComment &&
2862                         (gameMode == IcsObserving ||
2863                          gameMode == IcsPlayingWhite ||
2864                          gameMode == IcsPlayingBlack)) {
2865                       parse_pos = i - oldi;
2866                       memcpy(parse, &buf[oldi], parse_pos);
2867                       parse[parse_pos] = NULLCHAR;
2868                       started = STARTED_COMMENT;
2869                       savingComment = TRUE;
2870                     } else {
2871                       started = STARTED_CHATTER;
2872                       savingComment = FALSE;
2873                     }
2874                     loggedOn = TRUE;
2875                     continue;
2876                   }
2877                 }
2878
2879                 if (looking_at(buf, &i, "* s-shouts: ") ||
2880                     looking_at(buf, &i, "* c-shouts: ")) {
2881                     if (appData.colorize) {
2882                         if (oldi > next_out) {
2883                             SendToPlayer(&buf[next_out], oldi - next_out);
2884                             next_out = oldi;
2885                         }
2886                         Colorize(ColorSShout, FALSE);
2887                         curColor = ColorSShout;
2888                     }
2889                     loggedOn = TRUE;
2890                     started = STARTED_CHATTER;
2891                     continue;
2892                 }
2893
2894                 if (looking_at(buf, &i, "--->")) {
2895                     loggedOn = TRUE;
2896                     continue;
2897                 }
2898
2899                 if (looking_at(buf, &i, "* shouts: ") ||
2900                     looking_at(buf, &i, "--> ")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorShout, FALSE);
2907                         curColor = ColorShout;
2908                     }
2909                     loggedOn = TRUE;
2910                     started = STARTED_CHATTER;
2911                     continue;
2912                 }
2913
2914                 if (looking_at( buf, &i, "Challenge:")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorChallenge, FALSE);
2921                         curColor = ColorChallenge;
2922                     }
2923                     loggedOn = TRUE;
2924                     continue;
2925                 }
2926
2927                 if (looking_at(buf, &i, "* offers you") ||
2928                     looking_at(buf, &i, "* offers to be") ||
2929                     looking_at(buf, &i, "* would like to") ||
2930                     looking_at(buf, &i, "* requests to") ||
2931                     looking_at(buf, &i, "Your opponent offers") ||
2932                     looking_at(buf, &i, "Your opponent requests")) {
2933
2934                     if (appData.colorize) {
2935                         if (oldi > next_out) {
2936                             SendToPlayer(&buf[next_out], oldi - next_out);
2937                             next_out = oldi;
2938                         }
2939                         Colorize(ColorRequest, FALSE);
2940                         curColor = ColorRequest;
2941                     }
2942                     continue;
2943                 }
2944
2945                 if (looking_at(buf, &i, "* (*) seeking")) {
2946                     if (appData.colorize) {
2947                         if (oldi > next_out) {
2948                             SendToPlayer(&buf[next_out], oldi - next_out);
2949                             next_out = oldi;
2950                         }
2951                         Colorize(ColorSeek, FALSE);
2952                         curColor = ColorSeek;
2953                     }
2954                     continue;
2955             }
2956
2957             if (looking_at(buf, &i, "\\   ")) {
2958                 if (prevColor != ColorNormal) {
2959                     if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                     }
2963                     Colorize(prevColor, TRUE);
2964                     curColor = prevColor;
2965                 }
2966                 if (savingComment) {
2967                     parse_pos = i - oldi;
2968                     memcpy(parse, &buf[oldi], parse_pos);
2969                     parse[parse_pos] = NULLCHAR;
2970                     started = STARTED_COMMENT;
2971                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2972                         chattingPartner = savingComment - 3; // kludge to remember the box
2973                 } else {
2974                     started = STARTED_CHATTER;
2975                 }
2976                 continue;
2977             }
2978
2979             if (looking_at(buf, &i, "Black Strength :") ||
2980                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2981                 looking_at(buf, &i, "<10>") ||
2982                 looking_at(buf, &i, "#@#")) {
2983                 /* Wrong board style */
2984                 loggedOn = TRUE;
2985                 SendToICS(ics_prefix);
2986                 SendToICS("set style 12\n");
2987                 SendToICS(ics_prefix);
2988                 SendToICS("refresh\n");
2989                 continue;
2990             }
2991             
2992             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2993                 ICSInitScript();
2994                 have_sent_ICS_logon = 1;
2995                 continue;
2996             }
2997               
2998             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2999                 (looking_at(buf, &i, "\n<12> ") ||
3000                  looking_at(buf, &i, "<12> "))) {
3001                 loggedOn = TRUE;
3002                 if (oldi > next_out) {
3003                     SendToPlayer(&buf[next_out], oldi - next_out);
3004                 }
3005                 next_out = i;
3006                 started = STARTED_BOARD;
3007                 parse_pos = 0;
3008                 continue;
3009             }
3010
3011             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3012                 looking_at(buf, &i, "<b1> ")) {
3013                 if (oldi > next_out) {
3014                     SendToPlayer(&buf[next_out], oldi - next_out);
3015                 }
3016                 next_out = i;
3017                 started = STARTED_HOLDINGS;
3018                 parse_pos = 0;
3019                 continue;
3020             }
3021
3022             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3023                 loggedOn = TRUE;
3024                 /* Header for a move list -- first line */
3025
3026                 switch (ics_getting_history) {
3027                   case H_FALSE:
3028                     switch (gameMode) {
3029                       case IcsIdle:
3030                       case BeginningOfGame:
3031                         /* User typed "moves" or "oldmoves" while we
3032                            were idle.  Pretend we asked for these
3033                            moves and soak them up so user can step
3034                            through them and/or save them.
3035                            */
3036                         Reset(FALSE, TRUE);
3037                         gameMode = IcsObserving;
3038                         ModeHighlight();
3039                         ics_gamenum = -1;
3040                         ics_getting_history = H_GOT_UNREQ_HEADER;
3041                         break;
3042                       case EditGame: /*?*/
3043                       case EditPosition: /*?*/
3044                         /* Should above feature work in these modes too? */
3045                         /* For now it doesn't */
3046                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3047                         break;
3048                       default:
3049                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3050                         break;
3051                     }
3052                     break;
3053                   case H_REQUESTED:
3054                     /* Is this the right one? */
3055                     if (gameInfo.white && gameInfo.black &&
3056                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3057                         strcmp(gameInfo.black, star_match[2]) == 0) {
3058                         /* All is well */
3059                         ics_getting_history = H_GOT_REQ_HEADER;
3060                     }
3061                     break;
3062                   case H_GOT_REQ_HEADER:
3063                   case H_GOT_UNREQ_HEADER:
3064                   case H_GOT_UNWANTED_HEADER:
3065                   case H_GETTING_MOVES:
3066                     /* Should not happen */
3067                     DisplayError(_("Error gathering move list: two headers"), 0);
3068                     ics_getting_history = H_FALSE;
3069                     break;
3070                 }
3071
3072                 /* Save player ratings into gameInfo if needed */
3073                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3074                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3075                     (gameInfo.whiteRating == -1 ||
3076                      gameInfo.blackRating == -1)) {
3077
3078                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3079                     gameInfo.blackRating = string_to_rating(star_match[3]);
3080                     if (appData.debugMode)
3081                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3082                               gameInfo.whiteRating, gameInfo.blackRating);
3083                 }
3084                 continue;
3085             }
3086
3087             if (looking_at(buf, &i,
3088               "* * match, initial time: * minute*, increment: * second")) {
3089                 /* Header for a move list -- second line */
3090                 /* Initial board will follow if this is a wild game */
3091                 if (gameInfo.event != NULL) free(gameInfo.event);
3092                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3093                 gameInfo.event = StrSave(str);
3094                 /* [HGM] we switched variant. Translate boards if needed. */
3095                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i, "Move  ")) {
3100                 /* Beginning of a move list */
3101                 switch (ics_getting_history) {
3102                   case H_FALSE:
3103                     /* Normally should not happen */
3104                     /* Maybe user hit reset while we were parsing */
3105                     break;
3106                   case H_REQUESTED:
3107                     /* Happens if we are ignoring a move list that is not
3108                      * the one we just requested.  Common if the user
3109                      * tries to observe two games without turning off
3110                      * getMoveList */
3111                     break;
3112                   case H_GETTING_MOVES:
3113                     /* Should not happen */
3114                     DisplayError(_("Error gathering move list: nested"), 0);
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                   case H_GOT_REQ_HEADER:
3118                     ics_getting_history = H_GETTING_MOVES;
3119                     started = STARTED_MOVES;
3120                     parse_pos = 0;
3121                     if (oldi > next_out) {
3122                         SendToPlayer(&buf[next_out], oldi - next_out);
3123                     }
3124                     break;
3125                   case H_GOT_UNREQ_HEADER:
3126                     ics_getting_history = H_GETTING_MOVES;
3127                     started = STARTED_MOVES_NOHIDE;
3128                     parse_pos = 0;
3129                     break;
3130                   case H_GOT_UNWANTED_HEADER:
3131                     ics_getting_history = H_FALSE;
3132                     break;
3133                 }
3134                 continue;
3135             }                           
3136             
3137             if (looking_at(buf, &i, "% ") ||
3138                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3139                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3140                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3141                     soughtPending = FALSE;
3142                     seekGraphUp = TRUE;
3143                     DrawSeekGraph();
3144                 }
3145                 if(suppressKibitz) next_out = i;
3146                 savingComment = FALSE;
3147                 suppressKibitz = 0;
3148                 switch (started) {
3149                   case STARTED_MOVES:
3150                   case STARTED_MOVES_NOHIDE:
3151                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3152                     parse[parse_pos + i - oldi] = NULLCHAR;
3153                     ParseGameHistory(parse);
3154 #if ZIPPY
3155                     if (appData.zippyPlay && first.initDone) {
3156                         FeedMovesToProgram(&first, forwardMostMove);
3157                         if (gameMode == IcsPlayingWhite) {
3158                             if (WhiteOnMove(forwardMostMove)) {
3159                                 if (first.sendTime) {
3160                                   if (first.useColors) {
3161                                     SendToProgram("black\n", &first); 
3162                                   }
3163                                   SendTimeRemaining(&first, TRUE);
3164                                 }
3165                                 if (first.useColors) {
3166                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3167                                 }
3168                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3169                                 first.maybeThinking = TRUE;
3170                             } else {
3171                                 if (first.usePlayother) {
3172                                   if (first.sendTime) {
3173                                     SendTimeRemaining(&first, TRUE);
3174                                   }
3175                                   SendToProgram("playother\n", &first);
3176                                   firstMove = FALSE;
3177                                 } else {
3178                                   firstMove = TRUE;
3179                                 }
3180                             }
3181                         } else if (gameMode == IcsPlayingBlack) {
3182                             if (!WhiteOnMove(forwardMostMove)) {
3183                                 if (first.sendTime) {
3184                                   if (first.useColors) {
3185                                     SendToProgram("white\n", &first);
3186                                   }
3187                                   SendTimeRemaining(&first, FALSE);
3188                                 }
3189                                 if (first.useColors) {
3190                                   SendToProgram("black\n", &first);
3191                                 }
3192                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3193                                 first.maybeThinking = TRUE;
3194                             } else {
3195                                 if (first.usePlayother) {
3196                                   if (first.sendTime) {
3197                                     SendTimeRemaining(&first, FALSE);
3198                                   }
3199                                   SendToProgram("playother\n", &first);
3200                                   firstMove = FALSE;
3201                                 } else {
3202                                   firstMove = TRUE;
3203                                 }
3204                             }
3205                         }                       
3206                     }
3207 #endif
3208                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3209                         /* Moves came from oldmoves or moves command
3210                            while we weren't doing anything else.
3211                            */
3212                         currentMove = forwardMostMove;
3213                         ClearHighlights();/*!!could figure this out*/
3214                         flipView = appData.flipView;
3215                         DrawPosition(TRUE, boards[currentMove]);
3216                         DisplayBothClocks();
3217                         sprintf(str, "%s vs. %s",
3218                                 gameInfo.white, gameInfo.black);
3219                         DisplayTitle(str);
3220                         gameMode = IcsIdle;
3221                     } else {
3222                         /* Moves were history of an active game */
3223                         if (gameInfo.resultDetails != NULL) {
3224                             free(gameInfo.resultDetails);
3225                             gameInfo.resultDetails = NULL;
3226                         }
3227                     }
3228                     HistorySet(parseList, backwardMostMove,
3229                                forwardMostMove, currentMove-1);
3230                     DisplayMove(currentMove - 1);
3231                     if (started == STARTED_MOVES) next_out = i;
3232                     started = STARTED_NONE;
3233                     ics_getting_history = H_FALSE;
3234                     break;
3235
3236                   case STARTED_OBSERVE:
3237                     started = STARTED_NONE;
3238                     SendToICS(ics_prefix);
3239                     SendToICS("refresh\n");
3240                     break;
3241
3242                   default:
3243                     break;
3244                 }
3245                 if(bookHit) { // [HGM] book: simulate book reply
3246                     static char bookMove[MSG_SIZ]; // a bit generous?
3247
3248                     programStats.nodes = programStats.depth = programStats.time = 
3249                     programStats.score = programStats.got_only_move = 0;
3250                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3251
3252                     strcpy(bookMove, "move ");
3253                     strcat(bookMove, bookHit);
3254                     HandleMachineMove(bookMove, &first);
3255                 }
3256                 continue;
3257             }
3258             
3259             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3260                  started == STARTED_HOLDINGS ||
3261                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3262                 /* Accumulate characters in move list or board */
3263                 parse[parse_pos++] = buf[i];
3264             }
3265             
3266             /* Start of game messages.  Mostly we detect start of game
3267                when the first board image arrives.  On some versions
3268                of the ICS, though, we need to do a "refresh" after starting
3269                to observe in order to get the current board right away. */
3270             if (looking_at(buf, &i, "Adding game * to observation list")) {
3271                 started = STARTED_OBSERVE;
3272                 continue;
3273             }
3274
3275             /* Handle auto-observe */
3276             if (appData.autoObserve &&
3277                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3278                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3279                 char *player;
3280                 /* Choose the player that was highlighted, if any. */
3281                 if (star_match[0][0] == '\033' ||
3282                     star_match[1][0] != '\033') {
3283                     player = star_match[0];
3284                 } else {
3285                     player = star_match[2];
3286                 }
3287                 sprintf(str, "%sobserve %s\n",
3288                         ics_prefix, StripHighlightAndTitle(player));
3289                 SendToICS(str);
3290
3291                 /* Save ratings from notify string */
3292                 strcpy(player1Name, star_match[0]);
3293                 player1Rating = string_to_rating(star_match[1]);
3294                 strcpy(player2Name, star_match[2]);
3295                 player2Rating = string_to_rating(star_match[3]);
3296
3297                 if (appData.debugMode)
3298                   fprintf(debugFP, 
3299                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3300                           player1Name, player1Rating,
3301                           player2Name, player2Rating);
3302
3303                 continue;
3304             }
3305
3306             /* Deal with automatic examine mode after a game,
3307                and with IcsObserving -> IcsExamining transition */
3308             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3309                 looking_at(buf, &i, "has made you an examiner of game *")) {
3310
3311                 int gamenum = atoi(star_match[0]);
3312                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3313                     gamenum == ics_gamenum) {
3314                     /* We were already playing or observing this game;
3315                        no need to refetch history */
3316                     gameMode = IcsExamining;
3317                     if (pausing) {
3318                         pauseExamForwardMostMove = forwardMostMove;
3319                     } else if (currentMove < forwardMostMove) {
3320                         ForwardInner(forwardMostMove);
3321                     }
3322                 } else {
3323                     /* I don't think this case really can happen */
3324                     SendToICS(ics_prefix);
3325                     SendToICS("refresh\n");
3326                 }
3327                 continue;
3328             }    
3329             
3330             /* Error messages */
3331 //          if (ics_user_moved) {
3332             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3333                 if (looking_at(buf, &i, "Illegal move") ||
3334                     looking_at(buf, &i, "Not a legal move") ||
3335                     looking_at(buf, &i, "Your king is in check") ||
3336                     looking_at(buf, &i, "It isn't your turn") ||
3337                     looking_at(buf, &i, "It is not your move")) {
3338                     /* Illegal move */
3339                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3340                         currentMove = forwardMostMove-1;
3341                         DisplayMove(currentMove - 1); /* before DMError */
3342                         DrawPosition(FALSE, boards[currentMove]);
3343                         SwitchClocks(forwardMostMove-1); // [HGM] race
3344                         DisplayBothClocks();
3345                     }
3346                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3347                     ics_user_moved = 0;
3348                     continue;
3349                 }
3350             }
3351
3352             if (looking_at(buf, &i, "still have time") ||
3353                 looking_at(buf, &i, "not out of time") ||
3354                 looking_at(buf, &i, "either player is out of time") ||
3355                 looking_at(buf, &i, "has timeseal; checking")) {
3356                 /* We must have called his flag a little too soon */
3357                 whiteFlag = blackFlag = FALSE;
3358                 continue;
3359             }
3360
3361             if (looking_at(buf, &i, "added * seconds to") ||
3362                 looking_at(buf, &i, "seconds were added to")) {
3363                 /* Update the clocks */
3364                 SendToICS(ics_prefix);
3365                 SendToICS("refresh\n");
3366                 continue;
3367             }
3368
3369             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3370                 ics_clock_paused = TRUE;
3371                 StopClocks();
3372                 continue;
3373             }
3374
3375             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3376                 ics_clock_paused = FALSE;
3377                 StartClocks();
3378                 continue;
3379             }
3380
3381             /* Grab player ratings from the Creating: message.
3382                Note we have to check for the special case when
3383                the ICS inserts things like [white] or [black]. */
3384             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3385                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3386                 /* star_matches:
3387                    0    player 1 name (not necessarily white)
3388                    1    player 1 rating
3389                    2    empty, white, or black (IGNORED)
3390                    3    player 2 name (not necessarily black)
3391                    4    player 2 rating
3392                    
3393                    The names/ratings are sorted out when the game
3394                    actually starts (below).
3395                 */
3396                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3397                 player1Rating = string_to_rating(star_match[1]);
3398                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3399                 player2Rating = string_to_rating(star_match[4]);
3400
3401                 if (appData.debugMode)
3402                   fprintf(debugFP, 
3403                           "Ratings from 'Creating:' %s %d, %s %d\n",
3404                           player1Name, player1Rating,
3405                           player2Name, player2Rating);
3406
3407                 continue;
3408             }
3409             
3410             /* Improved generic start/end-of-game messages */
3411             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3412                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3413                 /* If tkind == 0: */
3414                 /* star_match[0] is the game number */
3415                 /*           [1] is the white player's name */
3416                 /*           [2] is the black player's name */
3417                 /* For end-of-game: */
3418                 /*           [3] is the reason for the game end */
3419                 /*           [4] is a PGN end game-token, preceded by " " */
3420                 /* For start-of-game: */
3421                 /*           [3] begins with "Creating" or "Continuing" */
3422                 /*           [4] is " *" or empty (don't care). */
3423                 int gamenum = atoi(star_match[0]);
3424                 char *whitename, *blackname, *why, *endtoken;
3425                 ChessMove endtype = (ChessMove) 0;
3426
3427                 if (tkind == 0) {
3428                   whitename = star_match[1];
3429                   blackname = star_match[2];
3430                   why = star_match[3];
3431                   endtoken = star_match[4];
3432                 } else {
3433                   whitename = star_match[1];
3434                   blackname = star_match[3];
3435                   why = star_match[5];
3436                   endtoken = star_match[6];
3437                 }
3438
3439                 /* Game start messages */
3440                 if (strncmp(why, "Creating ", 9) == 0 ||
3441                     strncmp(why, "Continuing ", 11) == 0) {
3442                     gs_gamenum = gamenum;
3443                     strcpy(gs_kind, strchr(why, ' ') + 1);
3444                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3445 #if ZIPPY
3446                     if (appData.zippyPlay) {
3447                         ZippyGameStart(whitename, blackname);
3448                     }
3449 #endif /*ZIPPY*/
3450                     continue;
3451                 }
3452
3453                 /* Game end messages */
3454                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3455                     ics_gamenum != gamenum) {
3456                     continue;
3457                 }
3458                 while (endtoken[0] == ' ') endtoken++;
3459                 switch (endtoken[0]) {
3460                   case '*':
3461                   default:
3462                     endtype = GameUnfinished;
3463                     break;
3464                   case '0':
3465                     endtype = BlackWins;
3466                     break;
3467                   case '1':
3468                     if (endtoken[1] == '/')
3469                       endtype = GameIsDrawn;
3470                     else
3471                       endtype = WhiteWins;
3472                     break;
3473                 }
3474                 GameEnds(endtype, why, GE_ICS);
3475 #if ZIPPY
3476                 if (appData.zippyPlay && first.initDone) {
3477                     ZippyGameEnd(endtype, why);
3478                     if (first.pr == NULL) {
3479                       /* Start the next process early so that we'll
3480                          be ready for the next challenge */
3481                       StartChessProgram(&first);
3482                     }
3483                     /* Send "new" early, in case this command takes
3484                        a long time to finish, so that we'll be ready
3485                        for the next challenge. */
3486                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3487                     Reset(TRUE, TRUE);
3488                 }
3489 #endif /*ZIPPY*/
3490                 continue;
3491             }
3492
3493             if (looking_at(buf, &i, "Removing game * from observation") ||
3494                 looking_at(buf, &i, "no longer observing game *") ||
3495                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3496                 if (gameMode == IcsObserving &&
3497                     atoi(star_match[0]) == ics_gamenum)
3498                   {
3499                       /* icsEngineAnalyze */
3500                       if (appData.icsEngineAnalyze) {
3501                             ExitAnalyzeMode();
3502                             ModeHighlight();
3503                       }
3504                       StopClocks();
3505                       gameMode = IcsIdle;
3506                       ics_gamenum = -1;
3507                       ics_user_moved = FALSE;
3508                   }
3509                 continue;
3510             }
3511
3512             if (looking_at(buf, &i, "no longer examining game *")) {
3513                 if (gameMode == IcsExamining &&
3514                     atoi(star_match[0]) == ics_gamenum)
3515                   {
3516                       gameMode = IcsIdle;
3517                       ics_gamenum = -1;
3518                       ics_user_moved = FALSE;
3519                   }
3520                 continue;
3521             }
3522
3523             /* Advance leftover_start past any newlines we find,
3524                so only partial lines can get reparsed */
3525             if (looking_at(buf, &i, "\n")) {
3526                 prevColor = curColor;
3527                 if (curColor != ColorNormal) {
3528                     if (oldi > next_out) {
3529                         SendToPlayer(&buf[next_out], oldi - next_out);
3530                         next_out = oldi;
3531                     }
3532                     Colorize(ColorNormal, FALSE);
3533                     curColor = ColorNormal;
3534                 }
3535                 if (started == STARTED_BOARD) {
3536                     started = STARTED_NONE;
3537                     parse[parse_pos] = NULLCHAR;
3538                     ParseBoard12(parse);
3539                     ics_user_moved = 0;
3540
3541                     /* Send premove here */
3542                     if (appData.premove) {
3543                       char str[MSG_SIZ];
3544                       if (currentMove == 0 &&
3545                           gameMode == IcsPlayingWhite &&
3546                           appData.premoveWhite) {
3547                         sprintf(str, "%s\n", appData.premoveWhiteText);
3548                         if (appData.debugMode)
3549                           fprintf(debugFP, "Sending premove:\n");
3550                         SendToICS(str);
3551                       } else if (currentMove == 1 &&
3552                                  gameMode == IcsPlayingBlack &&
3553                                  appData.premoveBlack) {
3554                         sprintf(str, "%s\n", appData.premoveBlackText);
3555                         if (appData.debugMode)
3556                           fprintf(debugFP, "Sending premove:\n");
3557                         SendToICS(str);
3558                       } else if (gotPremove) {
3559                         gotPremove = 0;
3560                         ClearPremoveHighlights();
3561                         if (appData.debugMode)
3562                           fprintf(debugFP, "Sending premove:\n");
3563                           UserMoveEvent(premoveFromX, premoveFromY, 
3564                                         premoveToX, premoveToY, 
3565                                         premovePromoChar);
3566                       }
3567                     }
3568
3569                     /* Usually suppress following prompt */
3570                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3571                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3572                         if (looking_at(buf, &i, "*% ")) {
3573                             savingComment = FALSE;
3574                             suppressKibitz = 0;
3575                         }
3576                     }
3577                     next_out = i;
3578                 } else if (started == STARTED_HOLDINGS) {
3579                     int gamenum;
3580                     char new_piece[MSG_SIZ];
3581                     started = STARTED_NONE;
3582                     parse[parse_pos] = NULLCHAR;
3583                     if (appData.debugMode)
3584                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3585                                                         parse, currentMove);
3586                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3587                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3588                         if (gameInfo.variant == VariantNormal) {
3589                           /* [HGM] We seem to switch variant during a game!
3590                            * Presumably no holdings were displayed, so we have
3591                            * to move the position two files to the right to
3592                            * create room for them!
3593                            */
3594                           VariantClass newVariant;
3595                           switch(gameInfo.boardWidth) { // base guess on board width
3596                                 case 9:  newVariant = VariantShogi; break;
3597                                 case 10: newVariant = VariantGreat; break;
3598                                 default: newVariant = VariantCrazyhouse; break;
3599                           }
3600                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3601                           /* Get a move list just to see the header, which
3602                              will tell us whether this is really bug or zh */
3603                           if (ics_getting_history == H_FALSE) {
3604                             ics_getting_history = H_REQUESTED;
3605                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3606                             SendToICS(str);
3607                           }
3608                         }
3609                         new_piece[0] = NULLCHAR;
3610                         sscanf(parse, "game %d white [%s black [%s <- %s",
3611                                &gamenum, white_holding, black_holding,
3612                                new_piece);
3613                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3614                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3615                         /* [HGM] copy holdings to board holdings area */
3616                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3617                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3618                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3619 #if ZIPPY
3620                         if (appData.zippyPlay && first.initDone) {
3621                             ZippyHoldings(white_holding, black_holding,
3622                                           new_piece);
3623                         }
3624 #endif /*ZIPPY*/
3625                         if (tinyLayout || smallLayout) {
3626                             char wh[16], bh[16];
3627                             PackHolding(wh, white_holding);
3628                             PackHolding(bh, black_holding);
3629                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3630                                     gameInfo.white, gameInfo.black);
3631                         } else {
3632                             sprintf(str, "%s [%s] vs. %s [%s]",
3633                                     gameInfo.white, white_holding,
3634                                     gameInfo.black, black_holding);
3635                         }
3636
3637                         DrawPosition(FALSE, boards[currentMove]);
3638                         DisplayTitle(str);
3639                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3640                         sscanf(parse, "game %d white [%s black [%s <- %s",
3641                                &gamenum, white_holding, black_holding,
3642                                new_piece);
3643                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3644                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3645                         /* [HGM] copy holdings to partner-board holdings area */
3646                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3647                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3648                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3649                       }
3650                     }
3651                     /* Suppress following prompt */
3652                     if (looking_at(buf, &i, "*% ")) {
3653                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3654                         savingComment = FALSE;
3655                         suppressKibitz = 0;
3656                     }
3657                     next_out = i;
3658                 }
3659                 continue;
3660             }
3661
3662             i++;                /* skip unparsed character and loop back */
3663         }
3664         
3665         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3666 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3667 //          SendToPlayer(&buf[next_out], i - next_out);
3668             started != STARTED_HOLDINGS && leftover_start > next_out) {
3669             SendToPlayer(&buf[next_out], leftover_start - next_out);
3670             next_out = i;
3671         }
3672         
3673         leftover_len = buf_len - leftover_start;
3674         /* if buffer ends with something we couldn't parse,
3675            reparse it after appending the next read */
3676         
3677     } else if (count == 0) {
3678         RemoveInputSource(isr);
3679         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3680     } else {
3681         DisplayFatalError(_("Error reading from ICS"), error, 1);
3682     }
3683 }
3684
3685
3686 /* Board style 12 looks like this:
3687    
3688    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3689    
3690  * The "<12> " is stripped before it gets to this routine.  The two
3691  * trailing 0's (flip state and clock ticking) are later addition, and
3692  * some chess servers may not have them, or may have only the first.
3693  * Additional trailing fields may be added in the future.  
3694  */
3695
3696 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3697
3698 #define RELATION_OBSERVING_PLAYED    0
3699 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3700 #define RELATION_PLAYING_MYMOVE      1
3701 #define RELATION_PLAYING_NOTMYMOVE  -1
3702 #define RELATION_EXAMINING           2
3703 #define RELATION_ISOLATED_BOARD     -3
3704 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3705
3706 void
3707 ParseBoard12(string)
3708      char *string;
3709
3710     GameMode newGameMode;
3711     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3712     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3713     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3714     char to_play, board_chars[200];
3715     char move_str[500], str[500], elapsed_time[500];
3716     char black[32], white[32];
3717     Board board;
3718     int prevMove = currentMove;
3719     int ticking = 2;
3720     ChessMove moveType;
3721     int fromX, fromY, toX, toY;
3722     char promoChar;
3723     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3724     char *bookHit = NULL; // [HGM] book
3725     Boolean weird = FALSE, reqFlag = FALSE;
3726
3727     fromX = fromY = toX = toY = -1;
3728     
3729     newGame = FALSE;
3730
3731     if (appData.debugMode)
3732       fprintf(debugFP, _("Parsing board: %s\n"), string);
3733
3734     move_str[0] = NULLCHAR;
3735     elapsed_time[0] = NULLCHAR;
3736     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3737         int  i = 0, j;
3738         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3739             if(string[i] == ' ') { ranks++; files = 0; }
3740             else files++;
3741             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3742             i++;
3743         }
3744         for(j = 0; j <i; j++) board_chars[j] = string[j];
3745         board_chars[i] = '\0';
3746         string += i + 1;
3747     }
3748     n = sscanf(string, PATTERN, &to_play, &double_push,
3749                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3750                &gamenum, white, black, &relation, &basetime, &increment,
3751                &white_stren, &black_stren, &white_time, &black_time,
3752                &moveNum, str, elapsed_time, move_str, &ics_flip,
3753                &ticking);
3754
3755     if (n < 21) {
3756         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3757         DisplayError(str, 0);
3758         return;
3759     }
3760
3761     /* Convert the move number to internal form */
3762     moveNum = (moveNum - 1) * 2;
3763     if (to_play == 'B') moveNum++;
3764     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3765       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3766                         0, 1);
3767       return;
3768     }
3769     
3770     switch (relation) {
3771       case RELATION_OBSERVING_PLAYED:
3772       case RELATION_OBSERVING_STATIC:
3773         if (gamenum == -1) {
3774             /* Old ICC buglet */
3775             relation = RELATION_OBSERVING_STATIC;
3776         }
3777         newGameMode = IcsObserving;
3778         break;
3779       case RELATION_PLAYING_MYMOVE:
3780       case RELATION_PLAYING_NOTMYMOVE:
3781         newGameMode =
3782           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3783             IcsPlayingWhite : IcsPlayingBlack;
3784         break;
3785       case RELATION_EXAMINING:
3786         newGameMode = IcsExamining;
3787         break;
3788       case RELATION_ISOLATED_BOARD:
3789       default:
3790         /* Just display this board.  If user was doing something else,
3791            we will forget about it until the next board comes. */ 
3792         newGameMode = IcsIdle;
3793         break;
3794       case RELATION_STARTING_POSITION:
3795         newGameMode = gameMode;
3796         break;
3797     }
3798     
3799     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3800          && newGameMode == IcsObserving && appData.bgObserve) {
3801       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3802       char buf[MSG_SIZ];
3803       for (k = 0; k < ranks; k++) {
3804         for (j = 0; j < files; j++)
3805           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3806         if(gameInfo.holdingsWidth > 1) {
3807              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3808              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3809         }
3810       }
3811       CopyBoard(partnerBoard, board);
3812       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3813       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3814                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3815       DisplayMessage(buf, "");
3816       return;
3817     }
3818
3819     /* Modify behavior for initial board display on move listing
3820        of wild games.
3821        */
3822     switch (ics_getting_history) {
3823       case H_FALSE:
3824       case H_REQUESTED:
3825         break;
3826       case H_GOT_REQ_HEADER:
3827       case H_GOT_UNREQ_HEADER:
3828         /* This is the initial position of the current game */
3829         gamenum = ics_gamenum;
3830         moveNum = 0;            /* old ICS bug workaround */
3831         if (to_play == 'B') {
3832           startedFromSetupPosition = TRUE;
3833           blackPlaysFirst = TRUE;
3834           moveNum = 1;
3835           if (forwardMostMove == 0) forwardMostMove = 1;
3836           if (backwardMostMove == 0) backwardMostMove = 1;
3837           if (currentMove == 0) currentMove = 1;
3838         }
3839         newGameMode = gameMode;
3840         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3841         break;
3842       case H_GOT_UNWANTED_HEADER:
3843         /* This is an initial board that we don't want */
3844         return;
3845       case H_GETTING_MOVES:
3846         /* Should not happen */
3847         DisplayError(_("Error gathering move list: extra board"), 0);
3848         ics_getting_history = H_FALSE;
3849         return;
3850     }
3851
3852    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3853                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3854      /* [HGM] We seem to have switched variant unexpectedly
3855       * Try to guess new variant from board size
3856       */
3857           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3858           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3859           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3860           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3861           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3862           if(!weird) newVariant = VariantNormal;
3863           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3864           /* Get a move list just to see the header, which
3865              will tell us whether this is really bug or zh */
3866           if (ics_getting_history == H_FALSE) {
3867             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3868             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3869             SendToICS(str);
3870           }
3871     }
3872     
3873     /* Take action if this is the first board of a new game, or of a
3874        different game than is currently being displayed.  */
3875     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3876         relation == RELATION_ISOLATED_BOARD) {
3877         
3878         /* Forget the old game and get the history (if any) of the new one */
3879         if (gameMode != BeginningOfGame) {
3880           Reset(TRUE, TRUE);
3881         }
3882         newGame = TRUE;
3883         if (appData.autoRaiseBoard) BoardToTop();
3884         prevMove = -3;
3885         if (gamenum == -1) {
3886             newGameMode = IcsIdle;
3887         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3888                    appData.getMoveList && !reqFlag) {
3889             /* Need to get game history */
3890             ics_getting_history = H_REQUESTED;
3891             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3892             SendToICS(str);
3893         }
3894         
3895         /* Initially flip the board to have black on the bottom if playing
3896            black or if the ICS flip flag is set, but let the user change
3897            it with the Flip View button. */
3898         flipView = appData.autoFlipView ? 
3899           (newGameMode == IcsPlayingBlack) || ics_flip :
3900           appData.flipView;
3901         
3902         /* Done with values from previous mode; copy in new ones */
3903         gameMode = newGameMode;
3904         ModeHighlight();
3905         ics_gamenum = gamenum;
3906         if (gamenum == gs_gamenum) {
3907             int klen = strlen(gs_kind);
3908             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3909             sprintf(str, "ICS %s", gs_kind);
3910             gameInfo.event = StrSave(str);
3911         } else {
3912             gameInfo.event = StrSave("ICS game");
3913         }
3914         gameInfo.site = StrSave(appData.icsHost);
3915         gameInfo.date = PGNDate();
3916         gameInfo.round = StrSave("-");
3917         gameInfo.white = StrSave(white);
3918         gameInfo.black = StrSave(black);
3919         timeControl = basetime * 60 * 1000;
3920         timeControl_2 = 0;
3921         timeIncrement = increment * 1000;
3922         movesPerSession = 0;
3923         gameInfo.timeControl = TimeControlTagValue();
3924         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3925   if (appData.debugMode) {
3926     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3927     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3928     setbuf(debugFP, NULL);
3929   }
3930
3931         gameInfo.outOfBook = NULL;
3932         
3933         /* Do we have the ratings? */
3934         if (strcmp(player1Name, white) == 0 &&
3935             strcmp(player2Name, black) == 0) {
3936             if (appData.debugMode)
3937               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3938                       player1Rating, player2Rating);
3939             gameInfo.whiteRating = player1Rating;
3940             gameInfo.blackRating = player2Rating;
3941         } else if (strcmp(player2Name, white) == 0 &&
3942                    strcmp(player1Name, black) == 0) {
3943             if (appData.debugMode)
3944               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3945                       player2Rating, player1Rating);
3946             gameInfo.whiteRating = player2Rating;
3947             gameInfo.blackRating = player1Rating;
3948         }
3949         player1Name[0] = player2Name[0] = NULLCHAR;
3950
3951         /* Silence shouts if requested */
3952         if (appData.quietPlay &&
3953             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3954             SendToICS(ics_prefix);
3955             SendToICS("set shout 0\n");
3956         }
3957     }
3958     
3959     /* Deal with midgame name changes */
3960     if (!newGame) {
3961         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3962             if (gameInfo.white) free(gameInfo.white);
3963             gameInfo.white = StrSave(white);
3964         }
3965         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3966             if (gameInfo.black) free(gameInfo.black);
3967             gameInfo.black = StrSave(black);
3968         }
3969     }
3970     
3971     /* Throw away game result if anything actually changes in examine mode */
3972     if (gameMode == IcsExamining && !newGame) {
3973         gameInfo.result = GameUnfinished;
3974         if (gameInfo.resultDetails != NULL) {
3975             free(gameInfo.resultDetails);
3976             gameInfo.resultDetails = NULL;
3977         }
3978     }
3979     
3980     /* In pausing && IcsExamining mode, we ignore boards coming
3981        in if they are in a different variation than we are. */
3982     if (pauseExamInvalid) return;
3983     if (pausing && gameMode == IcsExamining) {
3984         if (moveNum <= pauseExamForwardMostMove) {
3985             pauseExamInvalid = TRUE;
3986             forwardMostMove = pauseExamForwardMostMove;
3987             return;
3988         }
3989     }
3990     
3991   if (appData.debugMode) {
3992     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3993   }
3994     /* Parse the board */
3995     for (k = 0; k < ranks; k++) {
3996       for (j = 0; j < files; j++)
3997         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3998       if(gameInfo.holdingsWidth > 1) {
3999            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4000            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4001       }
4002     }
4003     CopyBoard(boards[moveNum], board);
4004     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4005     if (moveNum == 0) {
4006         startedFromSetupPosition =
4007           !CompareBoards(board, initialPosition);
4008         if(startedFromSetupPosition)
4009             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4010     }
4011
4012     /* [HGM] Set castling rights. Take the outermost Rooks,
4013        to make it also work for FRC opening positions. Note that board12
4014        is really defective for later FRC positions, as it has no way to
4015        indicate which Rook can castle if they are on the same side of King.
4016        For the initial position we grant rights to the outermost Rooks,
4017        and remember thos rights, and we then copy them on positions
4018        later in an FRC game. This means WB might not recognize castlings with
4019        Rooks that have moved back to their original position as illegal,
4020        but in ICS mode that is not its job anyway.
4021     */
4022     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4023     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4024
4025         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4026             if(board[0][i] == WhiteRook) j = i;
4027         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4028         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4029             if(board[0][i] == WhiteRook) j = i;
4030         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4031         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4032             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4033         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4034         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4035             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4036         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4037
4038         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4039         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4040             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4041         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4042             if(board[BOARD_HEIGHT-1][k] == bKing)
4043                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4044         if(gameInfo.variant == VariantTwoKings) {
4045             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4046             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4047             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4048         }
4049     } else { int r;
4050         r = boards[moveNum][CASTLING][0] = initialRights[0];
4051         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4052         r = boards[moveNum][CASTLING][1] = initialRights[1];
4053         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4054         r = boards[moveNum][CASTLING][3] = initialRights[3];
4055         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4056         r = boards[moveNum][CASTLING][4] = initialRights[4];
4057         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4058         /* wildcastle kludge: always assume King has rights */
4059         r = boards[moveNum][CASTLING][2] = initialRights[2];
4060         r = boards[moveNum][CASTLING][5] = initialRights[5];
4061     }
4062     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4063     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4064
4065     
4066     if (ics_getting_history == H_GOT_REQ_HEADER ||
4067         ics_getting_history == H_GOT_UNREQ_HEADER) {
4068         /* This was an initial position from a move list, not
4069            the current position */
4070         return;
4071     }
4072     
4073     /* Update currentMove and known move number limits */
4074     newMove = newGame || moveNum > forwardMostMove;
4075
4076     if (newGame) {
4077         forwardMostMove = backwardMostMove = currentMove = moveNum;
4078         if (gameMode == IcsExamining && moveNum == 0) {
4079           /* Workaround for ICS limitation: we are not told the wild
4080              type when starting to examine a game.  But if we ask for
4081              the move list, the move list header will tell us */
4082             ics_getting_history = H_REQUESTED;
4083             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4084             SendToICS(str);
4085         }
4086     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4087                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4088 #if ZIPPY
4089         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4090         /* [HGM] applied this also to an engine that is silently watching        */
4091         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4092             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4093             gameInfo.variant == currentlyInitializedVariant) {
4094           takeback = forwardMostMove - moveNum;
4095           for (i = 0; i < takeback; i++) {
4096             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4097             SendToProgram("undo\n", &first);
4098           }
4099         }
4100 #endif
4101
4102         forwardMostMove = moveNum;
4103         if (!pausing || currentMove > forwardMostMove)
4104           currentMove = forwardMostMove;
4105     } else {
4106         /* New part of history that is not contiguous with old part */ 
4107         if (pausing && gameMode == IcsExamining) {
4108             pauseExamInvalid = TRUE;
4109             forwardMostMove = pauseExamForwardMostMove;
4110             return;
4111         }
4112         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4113 #if ZIPPY
4114             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4115                 // [HGM] when we will receive the move list we now request, it will be
4116                 // fed to the engine from the first move on. So if the engine is not
4117                 // in the initial position now, bring it there.
4118                 InitChessProgram(&first, 0);
4119             }
4120 #endif
4121             ics_getting_history = H_REQUESTED;
4122             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4123             SendToICS(str);
4124         }
4125         forwardMostMove = backwardMostMove = currentMove = moveNum;
4126     }
4127     
4128     /* Update the clocks */
4129     if (strchr(elapsed_time, '.')) {
4130       /* Time is in ms */
4131       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4132       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4133     } else {
4134       /* Time is in seconds */
4135       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4136       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4137     }
4138       
4139
4140 #if ZIPPY
4141     if (appData.zippyPlay && newGame &&
4142         gameMode != IcsObserving && gameMode != IcsIdle &&
4143         gameMode != IcsExamining)
4144       ZippyFirstBoard(moveNum, basetime, increment);
4145 #endif
4146     
4147     /* Put the move on the move list, first converting
4148        to canonical algebraic form. */
4149     if (moveNum > 0) {
4150   if (appData.debugMode) {
4151     if (appData.debugMode) { int f = forwardMostMove;
4152         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4153                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4154                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4155     }
4156     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4157     fprintf(debugFP, "moveNum = %d\n", moveNum);
4158     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4159     setbuf(debugFP, NULL);
4160   }
4161         if (moveNum <= backwardMostMove) {
4162             /* We don't know what the board looked like before
4163                this move.  Punt. */
4164             strcpy(parseList[moveNum - 1], move_str);
4165             strcat(parseList[moveNum - 1], " ");
4166             strcat(parseList[moveNum - 1], elapsed_time);
4167             moveList[moveNum - 1][0] = NULLCHAR;
4168         } else if (strcmp(move_str, "none") == 0) {
4169             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4170             /* Again, we don't know what the board looked like;
4171                this is really the start of the game. */
4172             parseList[moveNum - 1][0] = NULLCHAR;
4173             moveList[moveNum - 1][0] = NULLCHAR;
4174             backwardMostMove = moveNum;
4175             startedFromSetupPosition = TRUE;
4176             fromX = fromY = toX = toY = -1;
4177         } else {
4178           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4179           //                 So we parse the long-algebraic move string in stead of the SAN move
4180           int valid; char buf[MSG_SIZ], *prom;
4181
4182           // str looks something like "Q/a1-a2"; kill the slash
4183           if(str[1] == '/') 
4184                 sprintf(buf, "%c%s", str[0], str+2);
4185           else  strcpy(buf, str); // might be castling
4186           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4187                 strcat(buf, prom); // long move lacks promo specification!
4188           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4189                 if(appData.debugMode) 
4190                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4191                 strcpy(move_str, buf);
4192           }
4193           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4194                                 &fromX, &fromY, &toX, &toY, &promoChar)
4195                || ParseOneMove(buf, moveNum - 1, &moveType,
4196                                 &fromX, &fromY, &toX, &toY, &promoChar);
4197           // end of long SAN patch
4198           if (valid) {
4199             (void) CoordsToAlgebraic(boards[moveNum - 1],
4200                                      PosFlags(moveNum - 1),
4201                                      fromY, fromX, toY, toX, promoChar,
4202                                      parseList[moveNum-1]);
4203             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4204               case MT_NONE:
4205               case MT_STALEMATE:
4206               default:
4207                 break;
4208               case MT_CHECK:
4209                 if(gameInfo.variant != VariantShogi)
4210                     strcat(parseList[moveNum - 1], "+");
4211                 break;
4212               case MT_CHECKMATE:
4213               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4214                 strcat(parseList[moveNum - 1], "#");
4215                 break;
4216             }
4217             strcat(parseList[moveNum - 1], " ");
4218             strcat(parseList[moveNum - 1], elapsed_time);
4219             /* currentMoveString is set as a side-effect of ParseOneMove */
4220             strcpy(moveList[moveNum - 1], currentMoveString);
4221             strcat(moveList[moveNum - 1], "\n");
4222           } else {
4223             /* Move from ICS was illegal!?  Punt. */
4224   if (appData.debugMode) {
4225     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4226     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4227   }
4228             strcpy(parseList[moveNum - 1], move_str);
4229             strcat(parseList[moveNum - 1], " ");
4230             strcat(parseList[moveNum - 1], elapsed_time);
4231             moveList[moveNum - 1][0] = NULLCHAR;
4232             fromX = fromY = toX = toY = -1;
4233           }
4234         }
4235   if (appData.debugMode) {
4236     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4237     setbuf(debugFP, NULL);
4238   }
4239
4240 #if ZIPPY
4241         /* Send move to chess program (BEFORE animating it). */
4242         if (appData.zippyPlay && !newGame && newMove && 
4243            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4244
4245             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4246                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4247                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4248                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4249                             move_str);
4250                     DisplayError(str, 0);
4251                 } else {
4252                     if (first.sendTime) {
4253                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4254                     }
4255                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4256                     if (firstMove && !bookHit) {
4257                         firstMove = FALSE;
4258                         if (first.useColors) {
4259                           SendToProgram(gameMode == IcsPlayingWhite ?
4260                                         "white\ngo\n" :
4261                                         "black\ngo\n", &first);
4262                         } else {
4263                           SendToProgram("go\n", &first);
4264                         }
4265                         first.maybeThinking = TRUE;
4266                     }
4267                 }
4268             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4269               if (moveList[moveNum - 1][0] == NULLCHAR) {
4270                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4271                 DisplayError(str, 0);
4272               } else {
4273                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4274                 SendMoveToProgram(moveNum - 1, &first);
4275               }
4276             }
4277         }
4278 #endif
4279     }
4280
4281     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4282         /* If move comes from a remote source, animate it.  If it
4283            isn't remote, it will have already been animated. */
4284         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4285             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4286         }
4287         if (!pausing && appData.highlightLastMove) {
4288             SetHighlights(fromX, fromY, toX, toY);
4289         }
4290     }
4291     
4292     /* Start the clocks */
4293     whiteFlag = blackFlag = FALSE;
4294     appData.clockMode = !(basetime == 0 && increment == 0);
4295     if (ticking == 0) {
4296       ics_clock_paused = TRUE;
4297       StopClocks();
4298     } else if (ticking == 1) {
4299       ics_clock_paused = FALSE;
4300     }
4301     if (gameMode == IcsIdle ||
4302         relation == RELATION_OBSERVING_STATIC ||
4303         relation == RELATION_EXAMINING ||
4304         ics_clock_paused)
4305       DisplayBothClocks();
4306     else
4307       StartClocks();
4308     
4309     /* Display opponents and material strengths */
4310     if (gameInfo.variant != VariantBughouse &&
4311         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4312         if (tinyLayout || smallLayout) {
4313             if(gameInfo.variant == VariantNormal)
4314                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4315                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4316                     basetime, increment);
4317             else
4318                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4319                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4320                     basetime, increment, (int) gameInfo.variant);
4321         } else {
4322             if(gameInfo.variant == VariantNormal)
4323                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4324                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4325                     basetime, increment);
4326             else
4327                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4328                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4329                     basetime, increment, VariantName(gameInfo.variant));
4330         }
4331         DisplayTitle(str);
4332   if (appData.debugMode) {
4333     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4334   }
4335     }
4336
4337
4338     /* Display the board */
4339     if (!pausing && !appData.noGUI) {
4340       
4341       if (appData.premove)
4342           if (!gotPremove || 
4343              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4344              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4345               ClearPremoveHighlights();
4346
4347       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4348       DrawPosition(j, boards[currentMove]);
4349
4350       DisplayMove(moveNum - 1);
4351       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4352             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4353               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4354         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4355       }
4356     }
4357
4358     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4359 #if ZIPPY
4360     if(bookHit) { // [HGM] book: simulate book reply
4361         static char bookMove[MSG_SIZ]; // a bit generous?
4362
4363         programStats.nodes = programStats.depth = programStats.time = 
4364         programStats.score = programStats.got_only_move = 0;
4365         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4366
4367         strcpy(bookMove, "move ");
4368         strcat(bookMove, bookHit);
4369         HandleMachineMove(bookMove, &first);
4370     }
4371 #endif
4372 }
4373
4374 void
4375 GetMoveListEvent()
4376 {
4377     char buf[MSG_SIZ];
4378     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4379         ics_getting_history = H_REQUESTED;
4380         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4381         SendToICS(buf);
4382     }
4383 }
4384
4385 void
4386 AnalysisPeriodicEvent(force)
4387      int force;
4388 {
4389     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4390          && !force) || !appData.periodicUpdates)
4391       return;
4392
4393     /* Send . command to Crafty to collect stats */
4394     SendToProgram(".\n", &first);
4395
4396     /* Don't send another until we get a response (this makes
4397        us stop sending to old Crafty's which don't understand
4398        the "." command (sending illegal cmds resets node count & time,
4399        which looks bad)) */
4400     programStats.ok_to_send = 0;
4401 }
4402
4403 void ics_update_width(new_width)
4404         int new_width;
4405 {
4406         ics_printf("set width %d\n", new_width);
4407 }
4408
4409 void
4410 SendMoveToProgram(moveNum, cps)
4411      int moveNum;
4412      ChessProgramState *cps;
4413 {
4414     char buf[MSG_SIZ];
4415
4416     if (cps->useUsermove) {
4417       SendToProgram("usermove ", cps);
4418     }
4419     if (cps->useSAN) {
4420       char *space;
4421       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4422         int len = space - parseList[moveNum];
4423         memcpy(buf, parseList[moveNum], len);
4424         buf[len++] = '\n';
4425         buf[len] = NULLCHAR;
4426       } else {
4427         sprintf(buf, "%s\n", parseList[moveNum]);
4428       }
4429       SendToProgram(buf, cps);
4430     } else {
4431       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4432         AlphaRank(moveList[moveNum], 4);
4433         SendToProgram(moveList[moveNum], cps);
4434         AlphaRank(moveList[moveNum], 4); // and back
4435       } else
4436       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4437        * the engine. It would be nice to have a better way to identify castle 
4438        * moves here. */
4439       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4440                                                                          && cps->useOOCastle) {
4441         int fromX = moveList[moveNum][0] - AAA; 
4442         int fromY = moveList[moveNum][1] - ONE;
4443         int toX = moveList[moveNum][2] - AAA; 
4444         int toY = moveList[moveNum][3] - ONE;
4445         if((boards[moveNum][fromY][fromX] == WhiteKing 
4446             && boards[moveNum][toY][toX] == WhiteRook)
4447            || (boards[moveNum][fromY][fromX] == BlackKing 
4448                && boards[moveNum][toY][toX] == BlackRook)) {
4449           if(toX > fromX) SendToProgram("O-O\n", cps);
4450           else SendToProgram("O-O-O\n", cps);
4451         }
4452         else SendToProgram(moveList[moveNum], cps);
4453       }
4454       else SendToProgram(moveList[moveNum], cps);
4455       /* End of additions by Tord */
4456     }
4457
4458     /* [HGM] setting up the opening has brought engine in force mode! */
4459     /*       Send 'go' if we are in a mode where machine should play. */
4460     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4461         (gameMode == TwoMachinesPlay   ||
4462 #ifdef ZIPPY
4463          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4464 #endif
4465          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4466         SendToProgram("go\n", cps);
4467   if (appData.debugMode) {
4468     fprintf(debugFP, "(extra)\n");
4469   }
4470     }
4471     setboardSpoiledMachineBlack = 0;
4472 }
4473
4474 void
4475 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4476      ChessMove moveType;
4477      int fromX, fromY, toX, toY;
4478 {
4479     char user_move[MSG_SIZ];
4480
4481     switch (moveType) {
4482       default:
4483         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4484                 (int)moveType, fromX, fromY, toX, toY);
4485         DisplayError(user_move + strlen("say "), 0);
4486         break;
4487       case WhiteKingSideCastle:
4488       case BlackKingSideCastle:
4489       case WhiteQueenSideCastleWild:
4490       case BlackQueenSideCastleWild:
4491       /* PUSH Fabien */
4492       case WhiteHSideCastleFR:
4493       case BlackHSideCastleFR:
4494       /* POP Fabien */
4495         sprintf(user_move, "o-o\n");
4496         break;
4497       case WhiteQueenSideCastle:
4498       case BlackQueenSideCastle:
4499       case WhiteKingSideCastleWild:
4500       case BlackKingSideCastleWild:
4501       /* PUSH Fabien */
4502       case WhiteASideCastleFR:
4503       case BlackASideCastleFR:
4504       /* POP Fabien */
4505         sprintf(user_move, "o-o-o\n");
4506         break;
4507       case WhitePromotionQueen:
4508       case BlackPromotionQueen:
4509       case WhitePromotionRook:
4510       case BlackPromotionRook:
4511       case WhitePromotionBishop:
4512       case BlackPromotionBishop:
4513       case WhitePromotionKnight:
4514       case BlackPromotionKnight:
4515       case WhitePromotionKing:
4516       case BlackPromotionKing:
4517       case WhitePromotionChancellor:
4518       case BlackPromotionChancellor:
4519       case WhitePromotionArchbishop:
4520       case BlackPromotionArchbishop:
4521         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4522             sprintf(user_move, "%c%c%c%c=%c\n",
4523                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4524                 PieceToChar(WhiteFerz));
4525         else if(gameInfo.variant == VariantGreat)
4526             sprintf(user_move, "%c%c%c%c=%c\n",
4527                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4528                 PieceToChar(WhiteMan));
4529         else
4530             sprintf(user_move, "%c%c%c%c=%c\n",
4531                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4532                 PieceToChar(PromoPiece(moveType)));
4533         break;
4534       case WhiteDrop:
4535       case BlackDrop:
4536         sprintf(user_move, "%c@%c%c\n",
4537                 ToUpper(PieceToChar((ChessSquare) fromX)),
4538                 AAA + toX, ONE + toY);
4539         break;
4540       case NormalMove:
4541       case WhiteCapturesEnPassant:
4542       case BlackCapturesEnPassant:
4543       case IllegalMove:  /* could be a variant we don't quite understand */
4544         sprintf(user_move, "%c%c%c%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4546         break;
4547     }
4548     SendToICS(user_move);
4549     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4550         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4551 }
4552
4553 void
4554 UploadGameEvent()
4555 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4556     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4557     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4558     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4559         DisplayError("You cannot do this while you are playing or observing", 0);
4560         return;
4561     }
4562     if(gameMode != IcsExamining) { // is this ever not the case?
4563         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4564
4565         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4566             sprintf(command, "match %s", ics_handle);
4567         } else { // on FICS we must first go to general examine mode
4568             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4569         }
4570         if(gameInfo.variant != VariantNormal) {
4571             // try figure out wild number, as xboard names are not always valid on ICS
4572             for(i=1; i<=36; i++) {
4573                 sprintf(buf, "wild/%d", i);
4574                 if(StringToVariant(buf) == gameInfo.variant) break;
4575             }
4576             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4577             else if(i == 22) sprintf(buf, "%s fr\n", command);
4578             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4579         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4580         SendToICS(ics_prefix);
4581         SendToICS(buf);
4582         if(startedFromSetupPosition || backwardMostMove != 0) {
4583           fen = PositionToFEN(backwardMostMove, NULL);
4584           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4585             sprintf(buf, "loadfen %s\n", fen);
4586             SendToICS(buf);
4587           } else { // FICS: everything has to set by separate bsetup commands
4588             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4589             sprintf(buf, "bsetup fen %s\n", fen);
4590             SendToICS(buf);
4591             if(!WhiteOnMove(backwardMostMove)) {
4592                 SendToICS("bsetup tomove black\n");
4593             }
4594             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4595             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4596             SendToICS(buf);
4597             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4598             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4599             SendToICS(buf);
4600             i = boards[backwardMostMove][EP_STATUS];
4601             if(i >= 0) { // set e.p.
4602                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4603                 SendToICS(buf);
4604             }
4605             bsetup++;
4606           }
4607         }
4608       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4609             SendToICS("bsetup done\n"); // switch to normal examining.
4610     }
4611     for(i = backwardMostMove; i<last; i++) {
4612         char buf[20];
4613         sprintf(buf, "%s\n", parseList[i]);
4614         SendToICS(buf);
4615     }
4616     SendToICS(ics_prefix);
4617     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4618 }
4619
4620 void
4621 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4622      int rf, ff, rt, ft;
4623      char promoChar;
4624      char move[7];
4625 {
4626     if (rf == DROP_RANK) {
4627         sprintf(move, "%c@%c%c\n",
4628                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4629     } else {
4630         if (promoChar == 'x' || promoChar == NULLCHAR) {
4631             sprintf(move, "%c%c%c%c\n",
4632                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4633         } else {
4634             sprintf(move, "%c%c%c%c%c\n",
4635                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4636         }
4637     }
4638 }
4639
4640 void
4641 ProcessICSInitScript(f)
4642      FILE *f;
4643 {
4644     char buf[MSG_SIZ];
4645
4646     while (fgets(buf, MSG_SIZ, f)) {
4647         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4648     }
4649
4650     fclose(f);
4651 }
4652
4653
4654 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4655 void
4656 AlphaRank(char *move, int n)
4657 {
4658 //    char *p = move, c; int x, y;
4659
4660     if (appData.debugMode) {
4661         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4662     }
4663
4664     if(move[1]=='*' && 
4665        move[2]>='0' && move[2]<='9' &&
4666        move[3]>='a' && move[3]<='x'    ) {
4667         move[1] = '@';
4668         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4669         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4670     } else
4671     if(move[0]>='0' && move[0]<='9' &&
4672        move[1]>='a' && move[1]<='x' &&
4673        move[2]>='0' && move[2]<='9' &&
4674        move[3]>='a' && move[3]<='x'    ) {
4675         /* input move, Shogi -> normal */
4676         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4677         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4678         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4679         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4680     } else
4681     if(move[1]=='@' &&
4682        move[3]>='0' && move[3]<='9' &&
4683        move[2]>='a' && move[2]<='x'    ) {
4684         move[1] = '*';
4685         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4686         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4687     } else
4688     if(
4689        move[0]>='a' && move[0]<='x' &&
4690        move[3]>='0' && move[3]<='9' &&
4691        move[2]>='a' && move[2]<='x'    ) {
4692          /* output move, normal -> Shogi */
4693         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4694         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4695         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4696         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4697         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4698     }
4699     if (appData.debugMode) {
4700         fprintf(debugFP, "   out = '%s'\n", move);
4701     }
4702 }
4703
4704 /* Parser for moves from gnuchess, ICS, or user typein box */
4705 Boolean
4706 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4707      char *move;
4708      int moveNum;
4709      ChessMove *moveType;
4710      int *fromX, *fromY, *toX, *toY;
4711      char *promoChar;
4712 {       
4713     if (appData.debugMode) {
4714         fprintf(debugFP, "move to parse: %s\n", move);
4715     }
4716     *moveType = yylexstr(moveNum, move);
4717
4718     switch (*moveType) {
4719       case WhitePromotionChancellor:
4720       case BlackPromotionChancellor:
4721       case WhitePromotionArchbishop:
4722       case BlackPromotionArchbishop:
4723       case WhitePromotionQueen:
4724       case BlackPromotionQueen:
4725       case WhitePromotionRook:
4726       case BlackPromotionRook:
4727       case WhitePromotionBishop:
4728       case BlackPromotionBishop:
4729       case WhitePromotionKnight:
4730       case BlackPromotionKnight:
4731       case WhitePromotionKing:
4732       case BlackPromotionKing:
4733       case NormalMove:
4734       case WhiteCapturesEnPassant:
4735       case BlackCapturesEnPassant:
4736       case WhiteKingSideCastle:
4737       case WhiteQueenSideCastle:
4738       case BlackKingSideCastle:
4739       case BlackQueenSideCastle:
4740       case WhiteKingSideCastleWild:
4741       case WhiteQueenSideCastleWild:
4742       case BlackKingSideCastleWild:
4743       case BlackQueenSideCastleWild:
4744       /* Code added by Tord: */
4745       case WhiteHSideCastleFR:
4746       case WhiteASideCastleFR:
4747       case BlackHSideCastleFR:
4748       case BlackASideCastleFR:
4749       /* End of code added by Tord */
4750       case IllegalMove:         /* bug or odd chess variant */
4751         *fromX = currentMoveString[0] - AAA;
4752         *fromY = currentMoveString[1] - ONE;
4753         *toX = currentMoveString[2] - AAA;
4754         *toY = currentMoveString[3] - ONE;
4755         *promoChar = currentMoveString[4];
4756         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4757             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4758     if (appData.debugMode) {
4759         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4760     }
4761             *fromX = *fromY = *toX = *toY = 0;
4762             return FALSE;
4763         }
4764         if (appData.testLegality) {
4765           return (*moveType != IllegalMove);
4766         } else {
4767           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4768                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4769         }
4770
4771       case WhiteDrop:
4772       case BlackDrop:
4773         *fromX = *moveType == WhiteDrop ?
4774           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4775           (int) CharToPiece(ToLower(currentMoveString[0]));
4776         *fromY = DROP_RANK;
4777         *toX = currentMoveString[2] - AAA;
4778         *toY = currentMoveString[3] - ONE;
4779         *promoChar = NULLCHAR;
4780         return TRUE;
4781
4782       case AmbiguousMove:
4783       case ImpossibleMove:
4784       case (ChessMove) 0:       /* end of file */
4785       case ElapsedTime:
4786       case Comment:
4787       case PGNTag:
4788       case NAG:
4789       case WhiteWins:
4790       case BlackWins:
4791       case GameIsDrawn:
4792       default:
4793     if (appData.debugMode) {
4794         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4795     }
4796         /* bug? */
4797         *fromX = *fromY = *toX = *toY = 0;
4798         *promoChar = NULLCHAR;
4799         return FALSE;
4800     }
4801 }
4802
4803
4804 void
4805 ParsePV(char *pv)
4806 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4807   int fromX, fromY, toX, toY; char promoChar;
4808   ChessMove moveType;
4809   Boolean valid;
4810   int nr = 0;
4811
4812   endPV = forwardMostMove;
4813   do {
4814     while(*pv == ' ') pv++;
4815     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4816     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4817 if(appData.debugMode){
4818 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4819 }
4820     if(!valid && nr == 0 &&
4821        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4822         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4823         // Hande case where played move is different from leading PV move
4824         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4825         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4826         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4827         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4828           endPV += 2; // if position different, keep this
4829           moveList[endPV-1][0] = fromX + AAA;
4830           moveList[endPV-1][1] = fromY + ONE;
4831           moveList[endPV-1][2] = toX + AAA;
4832           moveList[endPV-1][3] = toY + ONE;
4833           parseList[endPV-1][0] = NULLCHAR;
4834           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4835         }
4836       }
4837     }
4838     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4839     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4840     nr++;
4841     if(endPV+1 > framePtr) break; // no space, truncate
4842     if(!valid) break;
4843     endPV++;
4844     CopyBoard(boards[endPV], boards[endPV-1]);
4845     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4846     moveList[endPV-1][0] = fromX + AAA;
4847     moveList[endPV-1][1] = fromY + ONE;
4848     moveList[endPV-1][2] = toX + AAA;
4849     moveList[endPV-1][3] = toY + ONE;
4850     parseList[endPV-1][0] = NULLCHAR;
4851   } while(valid);
4852   currentMove = endPV;
4853   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4854   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4855                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4856   DrawPosition(TRUE, boards[currentMove]);
4857 }
4858
4859 static int lastX, lastY;
4860
4861 Boolean
4862 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4863 {
4864         int startPV;
4865
4866         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4867         lastX = x; lastY = y;
4868         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4869         startPV = index;
4870       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4871       index = startPV;
4872         while(buf[index] && buf[index] != '\n') index++;
4873         buf[index] = 0;
4874         ParsePV(buf+startPV);
4875         *start = startPV; *end = index-1;
4876         return TRUE;
4877 }
4878
4879 Boolean
4880 LoadPV(int x, int y)
4881 { // called on right mouse click to load PV
4882   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4883   lastX = x; lastY = y;
4884   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4885   return TRUE;
4886 }
4887
4888 void
4889 UnLoadPV()
4890 {
4891   if(endPV < 0) return;
4892   endPV = -1;
4893   currentMove = forwardMostMove;
4894   ClearPremoveHighlights();
4895   DrawPosition(TRUE, boards[currentMove]);
4896 }
4897
4898 void
4899 MovePV(int x, int y, int h)
4900 { // step through PV based on mouse coordinates (called on mouse move)
4901   int margin = h>>3, step = 0;
4902
4903   if(endPV < 0) return;
4904   // we must somehow check if right button is still down (might be released off board!)
4905   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4906   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4907   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4908   if(!step) return;
4909   lastX = x; lastY = y;
4910   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4911   currentMove += step;
4912   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4913   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4914                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4915   DrawPosition(FALSE, boards[currentMove]);
4916 }
4917
4918
4919 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4920 // All positions will have equal probability, but the current method will not provide a unique
4921 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4922 #define DARK 1
4923 #define LITE 2
4924 #define ANY 3
4925
4926 int squaresLeft[4];
4927 int piecesLeft[(int)BlackPawn];
4928 int seed, nrOfShuffles;
4929
4930 void GetPositionNumber()
4931 {       // sets global variable seed
4932         int i;
4933
4934         seed = appData.defaultFrcPosition;
4935         if(seed < 0) { // randomize based on time for negative FRC position numbers
4936                 for(i=0; i<50; i++) seed += random();
4937                 seed = random() ^ random() >> 8 ^ random() << 8;
4938                 if(seed<0) seed = -seed;
4939         }
4940 }
4941
4942 int put(Board board, int pieceType, int rank, int n, int shade)
4943 // put the piece on the (n-1)-th empty squares of the given shade
4944 {
4945         int i;
4946
4947         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4948                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4949                         board[rank][i] = (ChessSquare) pieceType;
4950                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4951                         squaresLeft[ANY]--;
4952                         piecesLeft[pieceType]--; 
4953                         return i;
4954                 }
4955         }
4956         return -1;
4957 }
4958
4959
4960 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4961 // calculate where the next piece goes, (any empty square), and put it there
4962 {
4963         int i;
4964
4965         i = seed % squaresLeft[shade];
4966         nrOfShuffles *= squaresLeft[shade];
4967         seed /= squaresLeft[shade];
4968         put(board, pieceType, rank, i, shade);
4969 }
4970
4971 void AddTwoPieces(Board board, int pieceType, int rank)
4972 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4973 {
4974         int i, n=squaresLeft[ANY], j=n-1, k;
4975
4976         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4977         i = seed % k;  // pick one
4978         nrOfShuffles *= k;
4979         seed /= k;
4980         while(i >= j) i -= j--;
4981         j = n - 1 - j; i += j;
4982         put(board, pieceType, rank, j, ANY);
4983         put(board, pieceType, rank, i, ANY);
4984 }
4985
4986 void SetUpShuffle(Board board, int number)
4987 {
4988         int i, p, first=1;
4989
4990         GetPositionNumber(); nrOfShuffles = 1;
4991
4992         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4993         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4994         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4995
4996         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4997
4998         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4999             p = (int) board[0][i];
5000             if(p < (int) BlackPawn) piecesLeft[p] ++;
5001             board[0][i] = EmptySquare;
5002         }
5003
5004         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5005             // shuffles restricted to allow normal castling put KRR first
5006             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5007                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5008             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5009                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5010             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5011                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5012             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5013                 put(board, WhiteRook, 0, 0, ANY);
5014             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5015         }
5016
5017         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5018             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5019             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5020                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5021                 while(piecesLeft[p] >= 2) {
5022                     AddOnePiece(board, p, 0, LITE);
5023                     AddOnePiece(board, p, 0, DARK);
5024                 }
5025                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5026             }
5027
5028         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5029             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5030             // but we leave King and Rooks for last, to possibly obey FRC restriction
5031             if(p == (int)WhiteRook) continue;
5032             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5033             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5034         }
5035
5036         // now everything is placed, except perhaps King (Unicorn) and Rooks
5037
5038         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5039             // Last King gets castling rights
5040             while(piecesLeft[(int)WhiteUnicorn]) {
5041                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5042                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5043             }
5044
5045             while(piecesLeft[(int)WhiteKing]) {
5046                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5047                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5048             }
5049
5050
5051         } else {
5052             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5053             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5054         }
5055
5056         // Only Rooks can be left; simply place them all
5057         while(piecesLeft[(int)WhiteRook]) {
5058                 i = put(board, WhiteRook, 0, 0, ANY);
5059                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5060                         if(first) {
5061                                 first=0;
5062                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5063                         }
5064                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5065                 }
5066         }
5067         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5068             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5069         }
5070
5071         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5072 }
5073
5074 int SetCharTable( char *table, const char * map )
5075 /* [HGM] moved here from winboard.c because of its general usefulness */
5076 /*       Basically a safe strcpy that uses the last character as King */
5077 {
5078     int result = FALSE; int NrPieces;
5079
5080     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5081                     && NrPieces >= 12 && !(NrPieces&1)) {
5082         int i; /* [HGM] Accept even length from 12 to 34 */
5083
5084         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5085         for( i=0; i<NrPieces/2-1; i++ ) {
5086             table[i] = map[i];
5087             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5088         }
5089         table[(int) WhiteKing]  = map[NrPieces/2-1];
5090         table[(int) BlackKing]  = map[NrPieces-1];
5091
5092         result = TRUE;
5093     }
5094
5095     return result;
5096 }
5097
5098 void Prelude(Board board)
5099 {       // [HGM] superchess: random selection of exo-pieces
5100         int i, j, k; ChessSquare p; 
5101         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5102
5103         GetPositionNumber(); // use FRC position number
5104
5105         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5106             SetCharTable(pieceToChar, appData.pieceToCharTable);
5107             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5108                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5109         }
5110
5111         j = seed%4;                 seed /= 4; 
5112         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5113         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5114         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5115         j = seed%3 + (seed%3 >= j); seed /= 3; 
5116         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5117         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5118         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5119         j = seed%3;                 seed /= 3; 
5120         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5121         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5122         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5123         j = seed%2 + (seed%2 >= j); seed /= 2; 
5124         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5125         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5126         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5127         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5128         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5129         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5130         put(board, exoPieces[0],    0, 0, ANY);
5131         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5132 }
5133
5134 void
5135 InitPosition(redraw)
5136      int redraw;
5137 {
5138     ChessSquare (* pieces)[BOARD_FILES];
5139     int i, j, pawnRow, overrule,
5140     oldx = gameInfo.boardWidth,
5141     oldy = gameInfo.boardHeight,
5142     oldh = gameInfo.holdingsWidth,
5143     oldv = gameInfo.variant;
5144
5145     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5146
5147     /* [AS] Initialize pv info list [HGM] and game status */
5148     {
5149         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5150             pvInfoList[i].depth = 0;
5151             boards[i][EP_STATUS] = EP_NONE;
5152             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5153         }
5154
5155         initialRulePlies = 0; /* 50-move counter start */
5156
5157         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5158         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5159     }
5160
5161     
5162     /* [HGM] logic here is completely changed. In stead of full positions */
5163     /* the initialized data only consist of the two backranks. The switch */
5164     /* selects which one we will use, which is than copied to the Board   */
5165     /* initialPosition, which for the rest is initialized by Pawns and    */
5166     /* empty squares. This initial position is then copied to boards[0],  */
5167     /* possibly after shuffling, so that it remains available.            */
5168
5169     gameInfo.holdingsWidth = 0; /* default board sizes */
5170     gameInfo.boardWidth    = 8;
5171     gameInfo.boardHeight   = 8;
5172     gameInfo.holdingsSize  = 0;
5173     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5174     for(i=0; i<BOARD_FILES-2; i++)
5175       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5176     initialPosition[EP_STATUS] = EP_NONE;
5177     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5178
5179     switch (gameInfo.variant) {
5180     case VariantFischeRandom:
5181       shuffleOpenings = TRUE;
5182     default:
5183       pieces = FIDEArray;
5184       break;
5185     case VariantShatranj:
5186       pieces = ShatranjArray;
5187       nrCastlingRights = 0;
5188       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5189       break;
5190     case VariantMakruk:
5191       pieces = makrukArray;
5192       nrCastlingRights = 0;
5193       startedFromSetupPosition = TRUE;
5194       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5195       break;
5196     case VariantTwoKings:
5197       pieces = twoKingsArray;
5198       break;
5199     case VariantCapaRandom:
5200       shuffleOpenings = TRUE;
5201     case VariantCapablanca:
5202       pieces = CapablancaArray;
5203       gameInfo.boardWidth = 10;
5204       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5205       break;
5206     case VariantGothic:
5207       pieces = GothicArray;
5208       gameInfo.boardWidth = 10;
5209       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5210       break;
5211     case VariantJanus:
5212       pieces = JanusArray;
5213       gameInfo.boardWidth = 10;
5214       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5215       nrCastlingRights = 6;
5216         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5217         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5218         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5219         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5220         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5221         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5222       break;
5223     case VariantFalcon:
5224       pieces = FalconArray;
5225       gameInfo.boardWidth = 10;
5226       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5227       break;
5228     case VariantXiangqi:
5229       pieces = XiangqiArray;
5230       gameInfo.boardWidth  = 9;
5231       gameInfo.boardHeight = 10;
5232       nrCastlingRights = 0;
5233       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5234       break;
5235     case VariantShogi:
5236       pieces = ShogiArray;
5237       gameInfo.boardWidth  = 9;
5238       gameInfo.boardHeight = 9;
5239       gameInfo.holdingsSize = 7;
5240       nrCastlingRights = 0;
5241       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5242       break;
5243     case VariantCourier:
5244       pieces = CourierArray;
5245       gameInfo.boardWidth  = 12;
5246       nrCastlingRights = 0;
5247       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5248       break;
5249     case VariantKnightmate:
5250       pieces = KnightmateArray;
5251       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5252       break;
5253     case VariantFairy:
5254       pieces = fairyArray;
5255       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5256       break;
5257     case VariantGreat:
5258       pieces = GreatArray;
5259       gameInfo.boardWidth = 10;
5260       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5261       gameInfo.holdingsSize = 8;
5262       break;
5263     case VariantSuper:
5264       pieces = FIDEArray;
5265       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5266       gameInfo.holdingsSize = 8;
5267       startedFromSetupPosition = TRUE;
5268       break;
5269     case VariantCrazyhouse:
5270     case VariantBughouse:
5271       pieces = FIDEArray;
5272       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5273       gameInfo.holdingsSize = 5;
5274       break;
5275     case VariantWildCastle:
5276       pieces = FIDEArray;
5277       /* !!?shuffle with kings guaranteed to be on d or e file */
5278       shuffleOpenings = 1;
5279       break;
5280     case VariantNoCastle:
5281       pieces = FIDEArray;
5282       nrCastlingRights = 0;
5283       /* !!?unconstrained back-rank shuffle */
5284       shuffleOpenings = 1;
5285       break;
5286     }
5287
5288     overrule = 0;
5289     if(appData.NrFiles >= 0) {
5290         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5291         gameInfo.boardWidth = appData.NrFiles;
5292     }
5293     if(appData.NrRanks >= 0) {
5294         gameInfo.boardHeight = appData.NrRanks;
5295     }
5296     if(appData.holdingsSize >= 0) {
5297         i = appData.holdingsSize;
5298         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5299         gameInfo.holdingsSize = i;
5300     }
5301     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5302     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5303         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5304
5305     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5306     if(pawnRow < 1) pawnRow = 1;
5307     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5308
5309     /* User pieceToChar list overrules defaults */
5310     if(appData.pieceToCharTable != NULL)
5311         SetCharTable(pieceToChar, appData.pieceToCharTable);
5312
5313     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5314
5315         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5316             s = (ChessSquare) 0; /* account holding counts in guard band */
5317         for( i=0; i<BOARD_HEIGHT; i++ )
5318             initialPosition[i][j] = s;
5319
5320         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5321         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5322         initialPosition[pawnRow][j] = WhitePawn;
5323         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5324         if(gameInfo.variant == VariantXiangqi) {
5325             if(j&1) {
5326                 initialPosition[pawnRow][j] = 
5327                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5328                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5329                    initialPosition[2][j] = WhiteCannon;
5330                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5331                 }
5332             }
5333         }
5334         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5335     }
5336     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5337
5338             j=BOARD_LEFT+1;
5339             initialPosition[1][j] = WhiteBishop;
5340             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5341             j=BOARD_RGHT-2;
5342             initialPosition[1][j] = WhiteRook;
5343             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5344     }
5345
5346     if( nrCastlingRights == -1) {
5347         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5348         /*       This sets default castling rights from none to normal corners   */
5349         /* Variants with other castling rights must set them themselves above    */
5350         nrCastlingRights = 6;
5351        
5352         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5353         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5354         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5355         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5356         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5357         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5358      }
5359
5360      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5361      if(gameInfo.variant == VariantGreat) { // promotion commoners
5362         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5363         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5364         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5365         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5366      }
5367   if (appData.debugMode) {
5368     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5369   }
5370     if(shuffleOpenings) {
5371         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5372         startedFromSetupPosition = TRUE;
5373     }
5374     if(startedFromPositionFile) {
5375       /* [HGM] loadPos: use PositionFile for every new game */
5376       CopyBoard(initialPosition, filePosition);
5377       for(i=0; i<nrCastlingRights; i++)
5378           initialRights[i] = filePosition[CASTLING][i];
5379       startedFromSetupPosition = TRUE;
5380     }
5381
5382     CopyBoard(boards[0], initialPosition);
5383
5384     if(oldx != gameInfo.boardWidth ||
5385        oldy != gameInfo.boardHeight ||
5386        oldh != gameInfo.holdingsWidth
5387 #ifdef GOTHIC
5388        || oldv == VariantGothic ||        // For licensing popups
5389        gameInfo.variant == VariantGothic
5390 #endif
5391 #ifdef FALCON
5392        || oldv == VariantFalcon ||
5393        gameInfo.variant == VariantFalcon
5394 #endif
5395                                          )
5396             InitDrawingSizes(-2 ,0);
5397
5398     if (redraw)
5399       DrawPosition(TRUE, boards[currentMove]);
5400 }
5401
5402 void
5403 SendBoard(cps, moveNum)
5404      ChessProgramState *cps;
5405      int moveNum;
5406 {
5407     char message[MSG_SIZ];
5408     
5409     if (cps->useSetboard) {
5410       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5411       sprintf(message, "setboard %s\n", fen);
5412       SendToProgram(message, cps);
5413       free(fen);
5414
5415     } else {
5416       ChessSquare *bp;
5417       int i, j;
5418       /* Kludge to set black to move, avoiding the troublesome and now
5419        * deprecated "black" command.
5420        */
5421       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5422
5423       SendToProgram("edit\n", cps);
5424       SendToProgram("#\n", cps);
5425       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5426         bp = &boards[moveNum][i][BOARD_LEFT];
5427         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5428           if ((int) *bp < (int) BlackPawn) {
5429             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5430                     AAA + j, ONE + i);
5431             if(message[0] == '+' || message[0] == '~') {
5432                 sprintf(message, "%c%c%c+\n",
5433                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5434                         AAA + j, ONE + i);
5435             }
5436             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5437                 message[1] = BOARD_RGHT   - 1 - j + '1';
5438                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5439             }
5440             SendToProgram(message, cps);
5441           }
5442         }
5443       }
5444     
5445       SendToProgram("c\n", cps);
5446       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5447         bp = &boards[moveNum][i][BOARD_LEFT];
5448         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5449           if (((int) *bp != (int) EmptySquare)
5450               && ((int) *bp >= (int) BlackPawn)) {
5451             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5452                     AAA + j, ONE + i);
5453             if(message[0] == '+' || message[0] == '~') {
5454                 sprintf(message, "%c%c%c+\n",
5455                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5456                         AAA + j, ONE + i);
5457             }
5458             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5459                 message[1] = BOARD_RGHT   - 1 - j + '1';
5460                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5461             }
5462             SendToProgram(message, cps);
5463           }
5464         }
5465       }
5466     
5467       SendToProgram(".\n", cps);
5468     }
5469     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5470 }
5471
5472 static int autoQueen; // [HGM] oneclick
5473
5474 int
5475 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5476 {
5477     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5478     /* [HGM] add Shogi promotions */
5479     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5480     ChessSquare piece;
5481     ChessMove moveType;
5482     Boolean premove;
5483
5484     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5485     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5486
5487     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5488       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5489         return FALSE;
5490
5491     piece = boards[currentMove][fromY][fromX];
5492     if(gameInfo.variant == VariantShogi) {
5493         promotionZoneSize = 3;
5494         highestPromotingPiece = (int)WhiteFerz;
5495     } else if(gameInfo.variant == VariantMakruk) {
5496         promotionZoneSize = 3;
5497     }
5498
5499     // next weed out all moves that do not touch the promotion zone at all
5500     if((int)piece >= BlackPawn) {
5501         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5502              return FALSE;
5503         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5504     } else {
5505         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5506            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5507     }
5508
5509     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5510
5511     // weed out mandatory Shogi promotions
5512     if(gameInfo.variant == VariantShogi) {
5513         if(piece >= BlackPawn) {
5514             if(toY == 0 && piece == BlackPawn ||
5515                toY == 0 && piece == BlackQueen ||
5516                toY <= 1 && piece == BlackKnight) {
5517                 *promoChoice = '+';
5518                 return FALSE;
5519             }
5520         } else {
5521             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5522                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5523                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5524                 *promoChoice = '+';
5525                 return FALSE;
5526             }
5527         }
5528     }
5529
5530     // weed out obviously illegal Pawn moves
5531     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5532         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5533         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5534         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5535         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5536         // note we are not allowed to test for valid (non-)capture, due to premove
5537     }
5538
5539     // we either have a choice what to promote to, or (in Shogi) whether to promote
5540     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5541         *promoChoice = PieceToChar(BlackFerz);  // no choice
5542         return FALSE;
5543     }
5544     if(autoQueen) { // predetermined
5545         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5546              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5547         else *promoChoice = PieceToChar(BlackQueen);
5548         return FALSE;
5549     }
5550
5551     // suppress promotion popup on illegal moves that are not premoves
5552     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5553               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5554     if(appData.testLegality && !premove) {
5555         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5556                         fromY, fromX, toY, toX, NULLCHAR);
5557         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5558            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5559             return FALSE;
5560     }
5561
5562     return TRUE;
5563 }
5564
5565 int
5566 InPalace(row, column)
5567      int row, column;
5568 {   /* [HGM] for Xiangqi */
5569     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5570          column < (BOARD_WIDTH + 4)/2 &&
5571          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5572     return FALSE;
5573 }
5574
5575 int
5576 PieceForSquare (x, y)
5577      int x;
5578      int y;
5579 {
5580   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5581      return -1;
5582   else
5583      return boards[currentMove][y][x];
5584 }
5585
5586 int
5587 OKToStartUserMove(x, y)
5588      int x, y;
5589 {
5590     ChessSquare from_piece;
5591     int white_piece;
5592
5593     if (matchMode) return FALSE;
5594     if (gameMode == EditPosition) return TRUE;
5595
5596     if (x >= 0 && y >= 0)
5597       from_piece = boards[currentMove][y][x];
5598     else
5599       from_piece = EmptySquare;
5600
5601     if (from_piece == EmptySquare) return FALSE;
5602
5603     white_piece = (int)from_piece >= (int)WhitePawn &&
5604       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5605
5606     switch (gameMode) {
5607       case PlayFromGameFile:
5608       case AnalyzeFile:
5609       case TwoMachinesPlay:
5610       case EndOfGame:
5611         return FALSE;
5612
5613       case IcsObserving:
5614       case IcsIdle:
5615         return FALSE;
5616
5617       case MachinePlaysWhite:
5618       case IcsPlayingBlack:
5619         if (appData.zippyPlay) return FALSE;
5620         if (white_piece) {
5621             DisplayMoveError(_("You are playing Black"));
5622             return FALSE;
5623         }
5624         break;
5625
5626       case MachinePlaysBlack:
5627       case IcsPlayingWhite:
5628         if (appData.zippyPlay) return FALSE;
5629         if (!white_piece) {
5630             DisplayMoveError(_("You are playing White"));
5631             return FALSE;
5632         }
5633         break;
5634
5635       case EditGame:
5636         if (!white_piece && WhiteOnMove(currentMove)) {
5637             DisplayMoveError(_("It is White's turn"));
5638             return FALSE;
5639         }           
5640         if (white_piece && !WhiteOnMove(currentMove)) {
5641             DisplayMoveError(_("It is Black's turn"));
5642             return FALSE;
5643         }           
5644         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5645             /* Editing correspondence game history */
5646             /* Could disallow this or prompt for confirmation */
5647             cmailOldMove = -1;
5648         }
5649         break;
5650
5651       case BeginningOfGame:
5652         if (appData.icsActive) return FALSE;
5653         if (!appData.noChessProgram) {
5654             if (!white_piece) {
5655                 DisplayMoveError(_("You are playing White"));
5656                 return FALSE;
5657             }
5658         }
5659         break;
5660         
5661       case Training:
5662         if (!white_piece && WhiteOnMove(currentMove)) {
5663             DisplayMoveError(_("It is White's turn"));
5664             return FALSE;
5665         }           
5666         if (white_piece && !WhiteOnMove(currentMove)) {
5667             DisplayMoveError(_("It is Black's turn"));
5668             return FALSE;
5669         }           
5670         break;
5671
5672       default:
5673       case IcsExamining:
5674         break;
5675     }
5676     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5677         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5678         && gameMode != AnalyzeFile && gameMode != Training) {
5679         DisplayMoveError(_("Displayed position is not current"));
5680         return FALSE;
5681     }
5682     return TRUE;
5683 }
5684
5685 Boolean
5686 OnlyMove(int *x, int *y, Boolean captures) {
5687     DisambiguateClosure cl;
5688     if (appData.zippyPlay) return FALSE;
5689     switch(gameMode) {
5690       case MachinePlaysBlack:
5691       case IcsPlayingWhite:
5692       case BeginningOfGame:
5693         if(!WhiteOnMove(currentMove)) return FALSE;
5694         break;
5695       case MachinePlaysWhite:
5696       case IcsPlayingBlack:
5697         if(WhiteOnMove(currentMove)) return FALSE;
5698         break;
5699       default:
5700         return FALSE;
5701     }
5702     cl.pieceIn = EmptySquare; 
5703     cl.rfIn = *y;
5704     cl.ffIn = *x;
5705     cl.rtIn = -1;
5706     cl.ftIn = -1;
5707     cl.promoCharIn = NULLCHAR;
5708     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5709     if( cl.kind == NormalMove ||
5710         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5711         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5712         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5713         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5714       fromX = cl.ff;
5715       fromY = cl.rf;
5716       *x = cl.ft;
5717       *y = cl.rt;
5718       return TRUE;
5719     }
5720     if(cl.kind != ImpossibleMove) return FALSE;
5721     cl.pieceIn = EmptySquare;
5722     cl.rfIn = -1;
5723     cl.ffIn = -1;
5724     cl.rtIn = *y;
5725     cl.ftIn = *x;
5726     cl.promoCharIn = NULLCHAR;
5727     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5728     if( cl.kind == NormalMove ||
5729         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5730         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5731         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5732         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5733       fromX = cl.ff;
5734       fromY = cl.rf;
5735       *x = cl.ft;
5736       *y = cl.rt;
5737       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5738       return TRUE;
5739     }
5740     return FALSE;
5741 }
5742
5743 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5744 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5745 int lastLoadGameUseList = FALSE;
5746 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5747 ChessMove lastLoadGameStart = (ChessMove) 0;
5748
5749 ChessMove
5750 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5751      int fromX, fromY, toX, toY;
5752      int promoChar;
5753      Boolean captureOwn;
5754 {
5755     ChessMove moveType;
5756     ChessSquare pdown, pup;
5757
5758     /* Check if the user is playing in turn.  This is complicated because we
5759        let the user "pick up" a piece before it is his turn.  So the piece he
5760        tried to pick up may have been captured by the time he puts it down!
5761        Therefore we use the color the user is supposed to be playing in this
5762        test, not the color of the piece that is currently on the starting
5763        square---except in EditGame mode, where the user is playing both
5764        sides; fortunately there the capture race can't happen.  (It can
5765        now happen in IcsExamining mode, but that's just too bad.  The user
5766        will get a somewhat confusing message in that case.)
5767        */
5768
5769     switch (gameMode) {
5770       case PlayFromGameFile:
5771       case AnalyzeFile:
5772       case TwoMachinesPlay:
5773       case EndOfGame:
5774       case IcsObserving:
5775       case IcsIdle:
5776         /* We switched into a game mode where moves are not accepted,
5777            perhaps while the mouse button was down. */
5778         return ImpossibleMove;
5779
5780       case MachinePlaysWhite:
5781         /* User is moving for Black */
5782         if (WhiteOnMove(currentMove)) {
5783             DisplayMoveError(_("It is White's turn"));
5784             return ImpossibleMove;
5785         }
5786         break;
5787
5788       case MachinePlaysBlack:
5789         /* User is moving for White */
5790         if (!WhiteOnMove(currentMove)) {
5791             DisplayMoveError(_("It is Black's turn"));
5792             return ImpossibleMove;
5793         }
5794         break;
5795
5796       case EditGame:
5797       case IcsExamining:
5798       case BeginningOfGame:
5799       case AnalyzeMode:
5800       case Training:
5801         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5802             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5803             /* User is moving for Black */
5804             if (WhiteOnMove(currentMove)) {
5805                 DisplayMoveError(_("It is White's turn"));
5806                 return ImpossibleMove;
5807             }
5808         } else {
5809             /* User is moving for White */
5810             if (!WhiteOnMove(currentMove)) {
5811                 DisplayMoveError(_("It is Black's turn"));
5812                 return ImpossibleMove;
5813             }
5814         }
5815         break;
5816
5817       case IcsPlayingBlack:
5818         /* User is moving for Black */
5819         if (WhiteOnMove(currentMove)) {
5820             if (!appData.premove) {
5821                 DisplayMoveError(_("It is White's turn"));
5822             } else if (toX >= 0 && toY >= 0) {
5823                 premoveToX = toX;
5824                 premoveToY = toY;
5825                 premoveFromX = fromX;
5826                 premoveFromY = fromY;
5827                 premovePromoChar = promoChar;
5828                 gotPremove = 1;
5829                 if (appData.debugMode) 
5830                     fprintf(debugFP, "Got premove: fromX %d,"
5831                             "fromY %d, toX %d, toY %d\n",
5832                             fromX, fromY, toX, toY);
5833             }
5834             return ImpossibleMove;
5835         }
5836         break;
5837
5838       case IcsPlayingWhite:
5839         /* User is moving for White */
5840         if (!WhiteOnMove(currentMove)) {
5841             if (!appData.premove) {
5842                 DisplayMoveError(_("It is Black's turn"));
5843             } else if (toX >= 0 && toY >= 0) {
5844                 premoveToX = toX;
5845                 premoveToY = toY;
5846                 premoveFromX = fromX;
5847                 premoveFromY = fromY;
5848                 premovePromoChar = promoChar;
5849                 gotPremove = 1;
5850                 if (appData.debugMode) 
5851                     fprintf(debugFP, "Got premove: fromX %d,"
5852                             "fromY %d, toX %d, toY %d\n",
5853                             fromX, fromY, toX, toY);
5854             }
5855             return ImpossibleMove;
5856         }
5857         break;
5858
5859       default:
5860         break;
5861
5862       case EditPosition:
5863         /* EditPosition, empty square, or different color piece;
5864            click-click move is possible */
5865         if (toX == -2 || toY == -2) {
5866             boards[0][fromY][fromX] = EmptySquare;
5867             return AmbiguousMove;
5868         } else if (toX >= 0 && toY >= 0) {
5869             boards[0][toY][toX] = boards[0][fromY][fromX];
5870             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5871                 if(boards[0][fromY][0] != EmptySquare) {
5872                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5873                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5874                 }
5875             } else
5876             if(fromX == BOARD_RGHT+1) {
5877                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5878                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5879                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5880                 }
5881             } else
5882             boards[0][fromY][fromX] = EmptySquare;
5883             return AmbiguousMove;
5884         }
5885         return ImpossibleMove;
5886     }
5887
5888     if(toX < 0 || toY < 0) return ImpossibleMove;
5889     pdown = boards[currentMove][fromY][fromX];
5890     pup = boards[currentMove][toY][toX];
5891
5892     /* [HGM] If move started in holdings, it means a drop */
5893     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5894          if( pup != EmptySquare ) return ImpossibleMove;
5895          if(appData.testLegality) {
5896              /* it would be more logical if LegalityTest() also figured out
5897               * which drops are legal. For now we forbid pawns on back rank.
5898               * Shogi is on its own here...
5899               */
5900              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5901                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5902                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5903          }
5904          return WhiteDrop; /* Not needed to specify white or black yet */
5905     }
5906
5907     /* [HGM] always test for legality, to get promotion info */
5908     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5909                                          fromY, fromX, toY, toX, promoChar);
5910     /* [HGM] but possibly ignore an IllegalMove result */
5911     if (appData.testLegality) {
5912         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5913             DisplayMoveError(_("Illegal move"));
5914             return ImpossibleMove;
5915         }
5916     }
5917
5918     return moveType;
5919     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5920        function is made into one that returns an OK move type if FinishMove
5921        should be called. This to give the calling driver routine the
5922        opportunity to finish the userMove input with a promotion popup,
5923        without bothering the user with this for invalid or illegal moves */
5924
5925 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5926 }
5927
5928 /* Common tail of UserMoveEvent and DropMenuEvent */
5929 int
5930 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5931      ChessMove moveType;
5932      int fromX, fromY, toX, toY;
5933      /*char*/int promoChar;
5934 {
5935     char *bookHit = 0;
5936
5937     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5938         // [HGM] superchess: suppress promotions to non-available piece
5939         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5940         if(WhiteOnMove(currentMove)) {
5941             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5942         } else {
5943             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5944         }
5945     }
5946
5947     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5948        move type in caller when we know the move is a legal promotion */
5949     if(moveType == NormalMove && promoChar)
5950         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5951
5952     /* [HGM] convert drag-and-drop piece drops to standard form */
5953     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5954          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5955            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5956                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5957            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5958            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5959            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5960            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5961          fromY = DROP_RANK;
5962     }
5963
5964     /* [HGM] <popupFix> The following if has been moved here from
5965        UserMoveEvent(). Because it seemed to belong here (why not allow
5966        piece drops in training games?), and because it can only be
5967        performed after it is known to what we promote. */
5968     if (gameMode == Training) {
5969       /* compare the move played on the board to the next move in the
5970        * game. If they match, display the move and the opponent's response. 
5971        * If they don't match, display an error message.
5972        */
5973       int saveAnimate;
5974       Board testBoard;
5975       CopyBoard(testBoard, boards[currentMove]);
5976       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5977
5978       if (CompareBoards(testBoard, boards[currentMove+1])) {
5979         ForwardInner(currentMove+1);
5980
5981         /* Autoplay the opponent's response.
5982          * if appData.animate was TRUE when Training mode was entered,
5983          * the response will be animated.
5984          */
5985         saveAnimate = appData.animate;
5986         appData.animate = animateTraining;
5987         ForwardInner(currentMove+1);
5988         appData.animate = saveAnimate;
5989
5990         /* check for the end of the game */
5991         if (currentMove >= forwardMostMove) {
5992           gameMode = PlayFromGameFile;
5993           ModeHighlight();
5994           SetTrainingModeOff();
5995           DisplayInformation(_("End of game"));
5996         }
5997       } else {
5998         DisplayError(_("Incorrect move"), 0);
5999       }
6000       return 1;
6001     }
6002
6003   /* Ok, now we know that the move is good, so we can kill
6004      the previous line in Analysis Mode */
6005   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6006                                 && currentMove < forwardMostMove) {
6007     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6008   }
6009
6010   /* If we need the chess program but it's dead, restart it */
6011   ResurrectChessProgram();
6012
6013   /* A user move restarts a paused game*/
6014   if (pausing)
6015     PauseEvent();
6016
6017   thinkOutput[0] = NULLCHAR;
6018
6019   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6020
6021   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6022
6023   if (gameMode == BeginningOfGame) {
6024     if (appData.noChessProgram) {
6025       gameMode = EditGame;
6026       SetGameInfo();
6027     } else {
6028       char buf[MSG_SIZ];
6029       gameMode = MachinePlaysBlack;
6030       StartClocks();
6031       SetGameInfo();
6032       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6033       DisplayTitle(buf);
6034       if (first.sendName) {
6035         sprintf(buf, "name %s\n", gameInfo.white);
6036         SendToProgram(buf, &first);
6037       }
6038       StartClocks();
6039     }
6040     ModeHighlight();
6041   }
6042
6043   /* Relay move to ICS or chess engine */
6044   if (appData.icsActive) {
6045     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6046         gameMode == IcsExamining) {
6047       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6048         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6049         SendToICS("draw ");
6050         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6051       }
6052       // also send plain move, in case ICS does not understand atomic claims
6053       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6054       ics_user_moved = 1;
6055     }
6056   } else {
6057     if (first.sendTime && (gameMode == BeginningOfGame ||
6058                            gameMode == MachinePlaysWhite ||
6059                            gameMode == MachinePlaysBlack)) {
6060       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6061     }
6062     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6063          // [HGM] book: if program might be playing, let it use book
6064         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6065         first.maybeThinking = TRUE;
6066     } else SendMoveToProgram(forwardMostMove-1, &first);
6067     if (currentMove == cmailOldMove + 1) {
6068       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6069     }
6070   }
6071
6072   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6073
6074   switch (gameMode) {
6075   case EditGame:
6076     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6077     case MT_NONE:
6078     case MT_CHECK:
6079       break;
6080     case MT_CHECKMATE:
6081     case MT_STAINMATE:
6082       if (WhiteOnMove(currentMove)) {
6083         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6084       } else {
6085         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6086       }
6087       break;
6088     case MT_STALEMATE:
6089       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6090       break;
6091     }
6092     break;
6093     
6094   case MachinePlaysBlack:
6095   case MachinePlaysWhite:
6096     /* disable certain menu options while machine is thinking */
6097     SetMachineThinkingEnables();
6098     break;
6099
6100   default:
6101     break;
6102   }
6103
6104   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6105         
6106   if(bookHit) { // [HGM] book: simulate book reply
6107         static char bookMove[MSG_SIZ]; // a bit generous?
6108
6109         programStats.nodes = programStats.depth = programStats.time = 
6110         programStats.score = programStats.got_only_move = 0;
6111         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6112
6113         strcpy(bookMove, "move ");
6114         strcat(bookMove, bookHit);
6115         HandleMachineMove(bookMove, &first);
6116   }
6117   return 1;
6118 }
6119
6120 void
6121 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6122      int fromX, fromY, toX, toY;
6123      int promoChar;
6124 {
6125     /* [HGM] This routine was added to allow calling of its two logical
6126        parts from other modules in the old way. Before, UserMoveEvent()
6127        automatically called FinishMove() if the move was OK, and returned
6128        otherwise. I separated the two, in order to make it possible to
6129        slip a promotion popup in between. But that it always needs two
6130        calls, to the first part, (now called UserMoveTest() ), and to
6131        FinishMove if the first part succeeded. Calls that do not need
6132        to do anything in between, can call this routine the old way. 
6133     */
6134     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6135 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6136     if(moveType == AmbiguousMove)
6137         DrawPosition(FALSE, boards[currentMove]);
6138     else if(moveType != ImpossibleMove && moveType != Comment)
6139         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6140 }
6141
6142 void
6143 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6144      Board board;
6145      int flags;
6146      ChessMove kind;
6147      int rf, ff, rt, ft;
6148      VOIDSTAR closure;
6149 {
6150     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6151     Markers *m = (Markers *) closure;
6152     if(rf == fromY && ff == fromX)
6153         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6154                          || kind == WhiteCapturesEnPassant
6155                          || kind == BlackCapturesEnPassant);
6156     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6157 }
6158
6159 void
6160 MarkTargetSquares(int clear)
6161 {
6162   int x, y;
6163   if(!appData.markers || !appData.highlightDragging || 
6164      !appData.testLegality || gameMode == EditPosition) return;
6165   if(clear) {
6166     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6167   } else {
6168     int capt = 0;
6169     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6170     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6171       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6172       if(capt)
6173       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6174     }
6175   }
6176   DrawPosition(TRUE, NULL);
6177 }
6178
6179 void LeftClick(ClickType clickType, int xPix, int yPix)
6180 {
6181     int x, y;
6182     Boolean saveAnimate;
6183     static int second = 0, promotionChoice = 0;
6184     char promoChoice = NULLCHAR;
6185
6186     if(appData.seekGraph && appData.icsActive && loggedOn &&
6187         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6188         SeekGraphClick(clickType, xPix, yPix, 0);
6189         return;
6190     }
6191
6192     if (clickType == Press) ErrorPopDown();
6193     MarkTargetSquares(1);
6194
6195     x = EventToSquare(xPix, BOARD_WIDTH);
6196     y = EventToSquare(yPix, BOARD_HEIGHT);
6197     if (!flipView && y >= 0) {
6198         y = BOARD_HEIGHT - 1 - y;
6199     }
6200     if (flipView && x >= 0) {
6201         x = BOARD_WIDTH - 1 - x;
6202     }
6203
6204     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6205         if(clickType == Release) return; // ignore upclick of click-click destination
6206         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6207         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6208         if(gameInfo.holdingsWidth && 
6209                 (WhiteOnMove(currentMove) 
6210                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6211                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6212             // click in right holdings, for determining promotion piece
6213             ChessSquare p = boards[currentMove][y][x];
6214             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6215             if(p != EmptySquare) {
6216                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6217                 fromX = fromY = -1;
6218                 return;
6219             }
6220         }
6221         DrawPosition(FALSE, boards[currentMove]);
6222         return;
6223     }
6224
6225     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6226     if(clickType == Press
6227             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6228               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6229               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6230         return;
6231
6232     autoQueen = appData.alwaysPromoteToQueen;
6233
6234     if (fromX == -1) {
6235       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6236         if (clickType == Press) {
6237             /* First square */
6238             if (OKToStartUserMove(x, y)) {
6239                 fromX = x;
6240                 fromY = y;
6241                 second = 0;
6242                 MarkTargetSquares(0);
6243                 DragPieceBegin(xPix, yPix);
6244                 if (appData.highlightDragging) {
6245                     SetHighlights(x, y, -1, -1);
6246                 }
6247             }
6248         }
6249         return;
6250       }
6251     }
6252
6253     /* fromX != -1 */
6254     if (clickType == Press && gameMode != EditPosition) {
6255         ChessSquare fromP;
6256         ChessSquare toP;
6257         int frc;
6258
6259         // ignore off-board to clicks
6260         if(y < 0 || x < 0) return;
6261
6262         /* Check if clicking again on the same color piece */
6263         fromP = boards[currentMove][fromY][fromX];
6264         toP = boards[currentMove][y][x];
6265         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6266         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6267              WhitePawn <= toP && toP <= WhiteKing &&
6268              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6269              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6270             (BlackPawn <= fromP && fromP <= BlackKing && 
6271              BlackPawn <= toP && toP <= BlackKing &&
6272              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6273              !(fromP == BlackKing && toP == BlackRook && frc))) {
6274             /* Clicked again on same color piece -- changed his mind */
6275             second = (x == fromX && y == fromY);
6276            if(!second || !OnlyMove(&x, &y, TRUE)) {
6277             if (appData.highlightDragging) {
6278                 SetHighlights(x, y, -1, -1);
6279             } else {
6280                 ClearHighlights();
6281             }
6282             if (OKToStartUserMove(x, y)) {
6283                 fromX = x;
6284                 fromY = y;
6285                 MarkTargetSquares(0);
6286                 DragPieceBegin(xPix, yPix);
6287             }
6288             return;
6289            }
6290         }
6291         // ignore clicks on holdings
6292         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6293     }
6294
6295     if (clickType == Release && x == fromX && y == fromY) {
6296         DragPieceEnd(xPix, yPix);
6297         if (appData.animateDragging) {
6298             /* Undo animation damage if any */
6299             DrawPosition(FALSE, NULL);
6300         }
6301         if (second) {
6302             /* Second up/down in same square; just abort move */
6303             second = 0;
6304             fromX = fromY = -1;
6305             ClearHighlights();
6306             gotPremove = 0;
6307             ClearPremoveHighlights();
6308         } else {
6309             /* First upclick in same square; start click-click mode */
6310             SetHighlights(x, y, -1, -1);
6311         }
6312         return;
6313     }
6314
6315     /* we now have a different from- and (possibly off-board) to-square */
6316     /* Completed move */
6317     toX = x;
6318     toY = y;
6319     saveAnimate = appData.animate;
6320     if (clickType == Press) {
6321         /* Finish clickclick move */
6322         if (appData.animate || appData.highlightLastMove) {
6323             SetHighlights(fromX, fromY, toX, toY);
6324         } else {
6325             ClearHighlights();
6326         }
6327     } else {
6328         /* Finish drag move */
6329         if (appData.highlightLastMove) {
6330             SetHighlights(fromX, fromY, toX, toY);
6331         } else {
6332             ClearHighlights();
6333         }
6334         DragPieceEnd(xPix, yPix);
6335         /* Don't animate move and drag both */
6336         appData.animate = FALSE;
6337     }
6338
6339     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6340     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6341         ChessSquare piece = boards[currentMove][fromY][fromX];
6342         if(gameMode == EditPosition && piece != EmptySquare &&
6343            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6344             int n;
6345              
6346             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6347                 n = PieceToNumber(piece - (int)BlackPawn);
6348                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6349                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6350                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6351             } else
6352             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6353                 n = PieceToNumber(piece);
6354                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6355                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6356                 boards[currentMove][n][BOARD_WIDTH-2]++;
6357             }
6358             boards[currentMove][fromY][fromX] = EmptySquare;
6359         }
6360         ClearHighlights();
6361         fromX = fromY = -1;
6362         DrawPosition(TRUE, boards[currentMove]);
6363         return;
6364     }
6365
6366     // off-board moves should not be highlighted
6367     if(x < 0 || x < 0) ClearHighlights();
6368
6369     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6370         SetHighlights(fromX, fromY, toX, toY);
6371         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6372             // [HGM] super: promotion to captured piece selected from holdings
6373             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6374             promotionChoice = TRUE;
6375             // kludge follows to temporarily execute move on display, without promoting yet
6376             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6377             boards[currentMove][toY][toX] = p;
6378             DrawPosition(FALSE, boards[currentMove]);
6379             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6380             boards[currentMove][toY][toX] = q;
6381             DisplayMessage("Click in holdings to choose piece", "");
6382             return;
6383         }
6384         PromotionPopUp();
6385     } else {
6386         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6387         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6388         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6389         fromX = fromY = -1;
6390     }
6391     appData.animate = saveAnimate;
6392     if (appData.animate || appData.animateDragging) {
6393         /* Undo animation damage if needed */
6394         DrawPosition(FALSE, NULL);
6395     }
6396 }
6397
6398 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6399 {   // front-end-free part taken out of PieceMenuPopup
6400     int whichMenu; int xSqr, ySqr;
6401
6402     if(seekGraphUp) { // [HGM] seekgraph
6403         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6404         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6405         return -2;
6406     }
6407
6408     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6409          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6410         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6411         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6412         return -2;
6413     }
6414
6415     xSqr = EventToSquare(x, BOARD_WIDTH);
6416     ySqr = EventToSquare(y, BOARD_HEIGHT);
6417     if (action == Release) UnLoadPV(); // [HGM] pv
6418     if (action != Press) return -2; // return code to be ignored
6419     switch (gameMode) {
6420       case IcsExamining:
6421         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6422       case EditPosition:
6423         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6424         if (xSqr < 0 || ySqr < 0) return -1;\r
6425         whichMenu = 0; // edit-position menu
6426         break;
6427       case IcsObserving:
6428         if(!appData.icsEngineAnalyze) return -1;
6429       case IcsPlayingWhite:
6430       case IcsPlayingBlack:
6431         if(!appData.zippyPlay) goto noZip;
6432       case AnalyzeMode:
6433       case AnalyzeFile:
6434       case MachinePlaysWhite:
6435       case MachinePlaysBlack:
6436       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6437         if (!appData.dropMenu) {
6438           LoadPV(x, y);
6439           return 2; // flag front-end to grab mouse events
6440         }
6441         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6442            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6443       case EditGame:
6444       noZip:
6445         if (xSqr < 0 || ySqr < 0) return -1;
6446         if (!appData.dropMenu || appData.testLegality &&
6447             gameInfo.variant != VariantBughouse &&
6448             gameInfo.variant != VariantCrazyhouse) return -1;
6449         whichMenu = 1; // drop menu
6450         break;
6451       default:
6452         return -1;
6453     }
6454
6455     if (((*fromX = xSqr) < 0) ||
6456         ((*fromY = ySqr) < 0)) {
6457         *fromX = *fromY = -1;
6458         return -1;
6459     }
6460     if (flipView)
6461       *fromX = BOARD_WIDTH - 1 - *fromX;
6462     else
6463       *fromY = BOARD_HEIGHT - 1 - *fromY;
6464
6465     return whichMenu;
6466 }
6467
6468 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6469 {
6470 //    char * hint = lastHint;
6471     FrontEndProgramStats stats;
6472
6473     stats.which = cps == &first ? 0 : 1;
6474     stats.depth = cpstats->depth;
6475     stats.nodes = cpstats->nodes;
6476     stats.score = cpstats->score;
6477     stats.time = cpstats->time;
6478     stats.pv = cpstats->movelist;
6479     stats.hint = lastHint;
6480     stats.an_move_index = 0;
6481     stats.an_move_count = 0;
6482
6483     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6484         stats.hint = cpstats->move_name;
6485         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6486         stats.an_move_count = cpstats->nr_moves;
6487     }
6488
6489     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6490
6491     SetProgramStats( &stats );
6492 }
6493
6494 int
6495 Adjudicate(ChessProgramState *cps)
6496 {       // [HGM] some adjudications useful with buggy engines
6497         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6498         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6499         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6500         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6501         int k, count = 0; static int bare = 1;
6502         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6503         Boolean canAdjudicate = !appData.icsActive;
6504
6505         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6506         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6507             if( appData.testLegality )
6508             {   /* [HGM] Some more adjudications for obstinate engines */
6509                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6510                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6511                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6512                 static int moveCount = 6;
6513                 ChessMove result;
6514                 char *reason = NULL;
6515
6516                 /* Count what is on board. */
6517                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6518                 {   ChessSquare p = boards[forwardMostMove][i][j];
6519                     int m=i;
6520
6521                     switch((int) p)
6522                     {   /* count B,N,R and other of each side */
6523                         case WhiteKing:
6524                         case BlackKing:
6525                              NrK++; break; // [HGM] atomic: count Kings
6526                         case WhiteKnight:
6527                              NrWN++; break;
6528                         case WhiteBishop:
6529                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6530                              bishopsColor |= 1 << ((i^j)&1);
6531                              NrWB++; break;
6532                         case BlackKnight:
6533                              NrBN++; break;
6534                         case BlackBishop:
6535                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6536                              bishopsColor |= 1 << ((i^j)&1);
6537                              NrBB++; break;
6538                         case WhiteRook:
6539                              NrWR++; break;
6540                         case BlackRook:
6541                              NrBR++; break;
6542                         case WhiteQueen:
6543                              NrWQ++; break;
6544                         case BlackQueen:
6545                              NrBQ++; break;
6546                         case EmptySquare: 
6547                              break;
6548                         case BlackPawn:
6549                              m = 7-i;
6550                         case WhitePawn:
6551                              PawnAdvance += m; NrPawns++;
6552                     }
6553                     NrPieces += (p != EmptySquare);
6554                     NrW += ((int)p < (int)BlackPawn);
6555                     if(gameInfo.variant == VariantXiangqi && 
6556                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6557                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6558                         NrW -= ((int)p < (int)BlackPawn);
6559                     }
6560                 }
6561
6562                 /* Some material-based adjudications that have to be made before stalemate test */
6563                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6564                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6565                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6566                      if(canAdjudicate && appData.checkMates) {
6567                          if(engineOpponent)
6568                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6569                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6570                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6571                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6572                          return 1;
6573                      }
6574                 }
6575
6576                 /* Bare King in Shatranj (loses) or Losers (wins) */
6577                 if( NrW == 1 || NrPieces - NrW == 1) {
6578                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6579                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6580                      if(canAdjudicate && appData.checkMates) {
6581                          if(engineOpponent)
6582                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6583                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6584                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6585                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6586                          return 1;
6587                      }
6588                   } else
6589                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6590                   {    /* bare King */
6591                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6592                         if(canAdjudicate && appData.checkMates) {
6593                             /* but only adjudicate if adjudication enabled */
6594                             if(engineOpponent)
6595                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6596                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6597                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6598                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6599                             return 1;
6600                         }
6601                   }
6602                 } else bare = 1;
6603
6604
6605             // don't wait for engine to announce game end if we can judge ourselves
6606             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6607               case MT_CHECK:
6608                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6609                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6610                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6611                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6612                             checkCnt++;
6613                         if(checkCnt >= 2) {
6614                             reason = "Xboard adjudication: 3rd check";
6615                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6616                             break;
6617                         }
6618                     }
6619                 }
6620               case MT_NONE:
6621               default:
6622                 break;
6623               case MT_STALEMATE:
6624               case MT_STAINMATE:
6625                 reason = "Xboard adjudication: Stalemate";
6626                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6627                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6628                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6629                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6630                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6631                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6632                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6633                                                                         EP_CHECKMATE : EP_WINS);
6634                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6635                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6636                 }
6637                 break;
6638               case MT_CHECKMATE:
6639                 reason = "Xboard adjudication: Checkmate";
6640                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6641                 break;
6642             }
6643
6644                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6645                     case EP_STALEMATE:
6646                         result = GameIsDrawn; break;
6647                     case EP_CHECKMATE:
6648                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6649                     case EP_WINS:
6650                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6651                     default:
6652                         result = (ChessMove) 0;
6653                 }
6654                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6655                     if(engineOpponent)
6656                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6657                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6658                     GameEnds( result, reason, GE_XBOARD );
6659                     return 1;
6660                 }
6661
6662                 /* Next absolutely insufficient mating material. */
6663                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6664                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6665                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6666                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6667                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6668
6669                      /* always flag draws, for judging claims */
6670                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6671
6672                      if(canAdjudicate && appData.materialDraws) {
6673                          /* but only adjudicate them if adjudication enabled */
6674                          if(engineOpponent) {
6675                            SendToProgram("force\n", engineOpponent); // suppress reply
6676                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6677                          }
6678                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6679                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6680                          return 1;
6681                      }
6682                 }
6683
6684                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6685                 if(NrPieces == 4 && 
6686                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6687                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6688                    || NrWN==2 || NrBN==2     /* KNNK */
6689                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6690                   ) ) {
6691                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6692                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6693                           if(engineOpponent) {
6694                             SendToProgram("force\n", engineOpponent); // suppress reply
6695                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6696                           }
6697                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6698                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6699                           return 1;
6700                      }
6701                 } else moveCount = 6;
6702             }
6703         }
6704           
6705         if (appData.debugMode) { int i;
6706             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6707                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6708                     appData.drawRepeats);
6709             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6710               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6711             
6712         }
6713
6714         // Repetition draws and 50-move rule can be applied independently of legality testing
6715
6716                 /* Check for rep-draws */
6717                 count = 0;
6718                 for(k = forwardMostMove-2;
6719                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6720                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6721                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6722                     k-=2)
6723                 {   int rights=0;
6724                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6725                         /* compare castling rights */
6726                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6727                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6728                                 rights++; /* King lost rights, while rook still had them */
6729                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6730                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6731                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6732                                    rights++; /* but at least one rook lost them */
6733                         }
6734                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6735                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6736                                 rights++; 
6737                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6738                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6739                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6740                                    rights++;
6741                         }
6742                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6743                             && appData.drawRepeats > 1) {
6744                              /* adjudicate after user-specified nr of repeats */
6745                              if(engineOpponent) {
6746                                SendToProgram("force\n", engineOpponent); // suppress reply
6747                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6748                              }
6749                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6751                                 // [HGM] xiangqi: check for forbidden perpetuals
6752                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6753                                 for(m=forwardMostMove; m>k; m-=2) {
6754                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6755                                         ourPerpetual = 0; // the current mover did not always check
6756                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6757                                         hisPerpetual = 0; // the opponent did not always check
6758                                 }
6759                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6760                                                                         ourPerpetual, hisPerpetual);
6761                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6762                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6763                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6764                                     return 1;
6765                                 }
6766                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6767                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6768                                 // Now check for perpetual chases
6769                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6770                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6771                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6772                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6773                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6774                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6775                                         return 1;
6776                                     }
6777                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6778                                         break; // Abort repetition-checking loop.
6779                                 }
6780                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6781                              }
6782                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6783                              return 1;
6784                         }
6785                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6786                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6787                     }
6788                 }
6789
6790                 /* Now we test for 50-move draws. Determine ply count */
6791                 count = forwardMostMove;
6792                 /* look for last irreversble move */
6793                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6794                     count--;
6795                 /* if we hit starting position, add initial plies */
6796                 if( count == backwardMostMove )
6797                     count -= initialRulePlies;
6798                 count = forwardMostMove - count; 
6799                 if( count >= 100)
6800                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6801                          /* this is used to judge if draw claims are legal */
6802                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6803                          if(engineOpponent) {
6804                            SendToProgram("force\n", engineOpponent); // suppress reply
6805                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6806                          }
6807                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6808                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6809                          return 1;
6810                 }
6811
6812                 /* if draw offer is pending, treat it as a draw claim
6813                  * when draw condition present, to allow engines a way to
6814                  * claim draws before making their move to avoid a race
6815                  * condition occurring after their move
6816                  */
6817                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6818                          char *p = NULL;
6819                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6820                              p = "Draw claim: 50-move rule";
6821                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6822                              p = "Draw claim: 3-fold repetition";
6823                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6824                              p = "Draw claim: insufficient mating material";
6825                          if( p != NULL && canAdjudicate) {
6826                              if(engineOpponent) {
6827                                SendToProgram("force\n", engineOpponent); // suppress reply
6828                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6829                              }
6830                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6832                              return 1;
6833                          }
6834                 }
6835
6836                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6837                     if(engineOpponent) {
6838                       SendToProgram("force\n", engineOpponent); // suppress reply
6839                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6840                     }
6841                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6842                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6843                     return 1;
6844                 }
6845         return 0;
6846 }
6847
6848 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6849 {   // [HGM] book: this routine intercepts moves to simulate book replies
6850     char *bookHit = NULL;
6851
6852     //first determine if the incoming move brings opponent into his book
6853     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6854         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6855     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6856     if(bookHit != NULL && !cps->bookSuspend) {
6857         // make sure opponent is not going to reply after receiving move to book position
6858         SendToProgram("force\n", cps);
6859         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6860     }
6861     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6862     // now arrange restart after book miss
6863     if(bookHit) {
6864         // after a book hit we never send 'go', and the code after the call to this routine
6865         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6866         char buf[MSG_SIZ];
6867         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6868         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6869         SendToProgram(buf, cps);
6870         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6871     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6872         SendToProgram("go\n", cps);
6873         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6874     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6875         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6876             SendToProgram("go\n", cps); 
6877         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6878     }
6879     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6880 }
6881
6882 char *savedMessage;
6883 ChessProgramState *savedState;
6884 void DeferredBookMove(void)
6885 {
6886         if(savedState->lastPing != savedState->lastPong)
6887                     ScheduleDelayedEvent(DeferredBookMove, 10);
6888         else
6889         HandleMachineMove(savedMessage, savedState);
6890 }
6891
6892 void
6893 HandleMachineMove(message, cps)
6894      char *message;
6895      ChessProgramState *cps;
6896 {
6897     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6898     char realname[MSG_SIZ];
6899     int fromX, fromY, toX, toY;
6900     ChessMove moveType;
6901     char promoChar;
6902     char *p;
6903     int machineWhite;
6904     char *bookHit;
6905
6906     cps->userError = 0;
6907
6908 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6909     /*
6910      * Kludge to ignore BEL characters
6911      */
6912     while (*message == '\007') message++;
6913
6914     /*
6915      * [HGM] engine debug message: ignore lines starting with '#' character
6916      */
6917     if(cps->debug && *message == '#') return;
6918
6919     /*
6920      * Look for book output
6921      */
6922     if (cps == &first && bookRequested) {
6923         if (message[0] == '\t' || message[0] == ' ') {
6924             /* Part of the book output is here; append it */
6925             strcat(bookOutput, message);
6926             strcat(bookOutput, "  \n");
6927             return;
6928         } else if (bookOutput[0] != NULLCHAR) {
6929             /* All of book output has arrived; display it */
6930             char *p = bookOutput;
6931             while (*p != NULLCHAR) {
6932                 if (*p == '\t') *p = ' ';
6933                 p++;
6934             }
6935             DisplayInformation(bookOutput);
6936             bookRequested = FALSE;
6937             /* Fall through to parse the current output */
6938         }
6939     }
6940
6941     /*
6942      * Look for machine move.
6943      */
6944     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6945         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6946     {
6947         /* This method is only useful on engines that support ping */
6948         if (cps->lastPing != cps->lastPong) {
6949           if (gameMode == BeginningOfGame) {
6950             /* Extra move from before last new; ignore */
6951             if (appData.debugMode) {
6952                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6953             }
6954           } else {
6955             if (appData.debugMode) {
6956                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6957                         cps->which, gameMode);
6958             }
6959
6960             SendToProgram("undo\n", cps);
6961           }
6962           return;
6963         }
6964
6965         switch (gameMode) {
6966           case BeginningOfGame:
6967             /* Extra move from before last reset; ignore */
6968             if (appData.debugMode) {
6969                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6970             }
6971             return;
6972
6973           case EndOfGame:
6974           case IcsIdle:
6975           default:
6976             /* Extra move after we tried to stop.  The mode test is
6977                not a reliable way of detecting this problem, but it's
6978                the best we can do on engines that don't support ping.
6979             */
6980             if (appData.debugMode) {
6981                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6982                         cps->which, gameMode);
6983             }
6984             SendToProgram("undo\n", cps);
6985             return;
6986
6987           case MachinePlaysWhite:
6988           case IcsPlayingWhite:
6989             machineWhite = TRUE;
6990             break;
6991
6992           case MachinePlaysBlack:
6993           case IcsPlayingBlack:
6994             machineWhite = FALSE;
6995             break;
6996
6997           case TwoMachinesPlay:
6998             machineWhite = (cps->twoMachinesColor[0] == 'w');
6999             break;
7000         }
7001         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7002             if (appData.debugMode) {
7003                 fprintf(debugFP,
7004                         "Ignoring move out of turn by %s, gameMode %d"
7005                         ", forwardMost %d\n",
7006                         cps->which, gameMode, forwardMostMove);
7007             }
7008             return;
7009         }
7010
7011     if (appData.debugMode) { int f = forwardMostMove;
7012         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7013                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7014                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7015     }
7016         if(cps->alphaRank) AlphaRank(machineMove, 4);
7017         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7018                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7019             /* Machine move could not be parsed; ignore it. */
7020             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7021                     machineMove, cps->which);
7022             DisplayError(buf1, 0);
7023             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7024                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7025             if (gameMode == TwoMachinesPlay) {
7026               GameEnds(machineWhite ? BlackWins : WhiteWins,
7027                        buf1, GE_XBOARD);
7028             }
7029             return;
7030         }
7031
7032         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7033         /* So we have to redo legality test with true e.p. status here,  */
7034         /* to make sure an illegal e.p. capture does not slip through,   */
7035         /* to cause a forfeit on a justified illegal-move complaint      */
7036         /* of the opponent.                                              */
7037         if( gameMode==TwoMachinesPlay && appData.testLegality
7038             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7039                                                               ) {
7040            ChessMove moveType;
7041            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7042                              fromY, fromX, toY, toX, promoChar);
7043             if (appData.debugMode) {
7044                 int i;
7045                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7046                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7047                 fprintf(debugFP, "castling rights\n");
7048             }
7049             if(moveType == IllegalMove) {
7050                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7051                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7052                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7053                            buf1, GE_XBOARD);
7054                 return;
7055            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7056            /* [HGM] Kludge to handle engines that send FRC-style castling
7057               when they shouldn't (like TSCP-Gothic) */
7058            switch(moveType) {
7059              case WhiteASideCastleFR:
7060              case BlackASideCastleFR:
7061                toX+=2;
7062                currentMoveString[2]++;
7063                break;
7064              case WhiteHSideCastleFR:
7065              case BlackHSideCastleFR:
7066                toX--;
7067                currentMoveString[2]--;
7068                break;
7069              default: ; // nothing to do, but suppresses warning of pedantic compilers
7070            }
7071         }
7072         hintRequested = FALSE;
7073         lastHint[0] = NULLCHAR;
7074         bookRequested = FALSE;
7075         /* Program may be pondering now */
7076         cps->maybeThinking = TRUE;
7077         if (cps->sendTime == 2) cps->sendTime = 1;
7078         if (cps->offeredDraw) cps->offeredDraw--;
7079
7080         /* currentMoveString is set as a side-effect of ParseOneMove */
7081         strcpy(machineMove, currentMoveString);
7082         strcat(machineMove, "\n");
7083         strcpy(moveList[forwardMostMove], machineMove);
7084
7085         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7086
7087         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7088         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7089             int count = 0;
7090
7091             while( count < adjudicateLossPlies ) {
7092                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7093
7094                 if( count & 1 ) {
7095                     score = -score; /* Flip score for winning side */
7096                 }
7097
7098                 if( score > adjudicateLossThreshold ) {
7099                     break;
7100                 }
7101
7102                 count++;
7103             }
7104
7105             if( count >= adjudicateLossPlies ) {
7106                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7107
7108                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7109                     "Xboard adjudication", 
7110                     GE_XBOARD );
7111
7112                 return;
7113             }
7114         }
7115
7116         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7117
7118 #if ZIPPY
7119         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7120             first.initDone) {
7121           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7122                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7123                 SendToICS("draw ");
7124                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7125           }
7126           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7127           ics_user_moved = 1;
7128           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7129                 char buf[3*MSG_SIZ];
7130
7131                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7132                         programStats.score / 100.,
7133                         programStats.depth,
7134                         programStats.time / 100.,
7135                         (unsigned int)programStats.nodes,
7136                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7137                         programStats.movelist);
7138                 SendToICS(buf);
7139 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7140           }
7141         }
7142 #endif
7143
7144         /* [AS] Save move info and clear stats for next move */
7145         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7146         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7147         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7148         ClearProgramStats();
7149         thinkOutput[0] = NULLCHAR;
7150         hiddenThinkOutputState = 0;
7151
7152         bookHit = NULL;
7153         if (gameMode == TwoMachinesPlay) {
7154             /* [HGM] relaying draw offers moved to after reception of move */
7155             /* and interpreting offer as claim if it brings draw condition */
7156             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7157                 SendToProgram("draw\n", cps->other);
7158             }
7159             if (cps->other->sendTime) {
7160                 SendTimeRemaining(cps->other,
7161                                   cps->other->twoMachinesColor[0] == 'w');
7162             }
7163             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7164             if (firstMove && !bookHit) {
7165                 firstMove = FALSE;
7166                 if (cps->other->useColors) {
7167                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7168                 }
7169                 SendToProgram("go\n", cps->other);
7170             }
7171             cps->other->maybeThinking = TRUE;
7172         }
7173
7174         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7175         
7176         if (!pausing && appData.ringBellAfterMoves) {
7177             RingBell();
7178         }
7179
7180         /* 
7181          * Reenable menu items that were disabled while
7182          * machine was thinking
7183          */
7184         if (gameMode != TwoMachinesPlay)
7185             SetUserThinkingEnables();
7186
7187         // [HGM] book: after book hit opponent has received move and is now in force mode
7188         // force the book reply into it, and then fake that it outputted this move by jumping
7189         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7190         if(bookHit) {
7191                 static char bookMove[MSG_SIZ]; // a bit generous?
7192
7193                 strcpy(bookMove, "move ");
7194                 strcat(bookMove, bookHit);
7195                 message = bookMove;
7196                 cps = cps->other;
7197                 programStats.nodes = programStats.depth = programStats.time = 
7198                 programStats.score = programStats.got_only_move = 0;
7199                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7200
7201                 if(cps->lastPing != cps->lastPong) {
7202                     savedMessage = message; // args for deferred call
7203                     savedState = cps;
7204                     ScheduleDelayedEvent(DeferredBookMove, 10);
7205                     return;
7206                 }
7207                 goto FakeBookMove;
7208         }
7209
7210         return;
7211     }
7212
7213     /* Set special modes for chess engines.  Later something general
7214      *  could be added here; for now there is just one kludge feature,
7215      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7216      *  when "xboard" is given as an interactive command.
7217      */
7218     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7219         cps->useSigint = FALSE;
7220         cps->useSigterm = FALSE;
7221     }
7222     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7223       ParseFeatures(message+8, cps);
7224       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7225     }
7226
7227     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7228      * want this, I was asked to put it in, and obliged.
7229      */
7230     if (!strncmp(message, "setboard ", 9)) {
7231         Board initial_position;
7232
7233         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7234
7235         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7236             DisplayError(_("Bad FEN received from engine"), 0);
7237             return ;
7238         } else {
7239            Reset(TRUE, FALSE);
7240            CopyBoard(boards[0], initial_position);
7241            initialRulePlies = FENrulePlies;
7242            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7243            else gameMode = MachinePlaysBlack;                 
7244            DrawPosition(FALSE, boards[currentMove]);
7245         }
7246         return;
7247     }
7248
7249     /*
7250      * Look for communication commands
7251      */
7252     if (!strncmp(message, "telluser ", 9)) {
7253         DisplayNote(message + 9);
7254         return;
7255     }
7256     if (!strncmp(message, "tellusererror ", 14)) {
7257         cps->userError = 1;
7258         DisplayError(message + 14, 0);
7259         return;
7260     }
7261     if (!strncmp(message, "tellopponent ", 13)) {
7262       if (appData.icsActive) {
7263         if (loggedOn) {
7264           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7265           SendToICS(buf1);
7266         }
7267       } else {
7268         DisplayNote(message + 13);
7269       }
7270       return;
7271     }
7272     if (!strncmp(message, "tellothers ", 11)) {
7273       if (appData.icsActive) {
7274         if (loggedOn) {
7275           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7276           SendToICS(buf1);
7277         }
7278       }
7279       return;
7280     }
7281     if (!strncmp(message, "tellall ", 8)) {
7282       if (appData.icsActive) {
7283         if (loggedOn) {
7284           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7285           SendToICS(buf1);
7286         }
7287       } else {
7288         DisplayNote(message + 8);
7289       }
7290       return;
7291     }
7292     if (strncmp(message, "warning", 7) == 0) {
7293         /* Undocumented feature, use tellusererror in new code */
7294         DisplayError(message, 0);
7295         return;
7296     }
7297     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7298         strcpy(realname, cps->tidy);
7299         strcat(realname, " query");
7300         AskQuestion(realname, buf2, buf1, cps->pr);
7301         return;
7302     }
7303     /* Commands from the engine directly to ICS.  We don't allow these to be 
7304      *  sent until we are logged on. Crafty kibitzes have been known to 
7305      *  interfere with the login process.
7306      */
7307     if (loggedOn) {
7308         if (!strncmp(message, "tellics ", 8)) {
7309             SendToICS(message + 8);
7310             SendToICS("\n");
7311             return;
7312         }
7313         if (!strncmp(message, "tellicsnoalias ", 15)) {
7314             SendToICS(ics_prefix);
7315             SendToICS(message + 15);
7316             SendToICS("\n");
7317             return;
7318         }
7319         /* The following are for backward compatibility only */
7320         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7321             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7322             SendToICS(ics_prefix);
7323             SendToICS(message);
7324             SendToICS("\n");
7325             return;
7326         }
7327     }
7328     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7329         return;
7330     }
7331     /*
7332      * If the move is illegal, cancel it and redraw the board.
7333      * Also deal with other error cases.  Matching is rather loose
7334      * here to accommodate engines written before the spec.
7335      */
7336     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7337         strncmp(message, "Error", 5) == 0) {
7338         if (StrStr(message, "name") || 
7339             StrStr(message, "rating") || StrStr(message, "?") ||
7340             StrStr(message, "result") || StrStr(message, "board") ||
7341             StrStr(message, "bk") || StrStr(message, "computer") ||
7342             StrStr(message, "variant") || StrStr(message, "hint") ||
7343             StrStr(message, "random") || StrStr(message, "depth") ||
7344             StrStr(message, "accepted")) {
7345             return;
7346         }
7347         if (StrStr(message, "protover")) {
7348           /* Program is responding to input, so it's apparently done
7349              initializing, and this error message indicates it is
7350              protocol version 1.  So we don't need to wait any longer
7351              for it to initialize and send feature commands. */
7352           FeatureDone(cps, 1);
7353           cps->protocolVersion = 1;
7354           return;
7355         }
7356         cps->maybeThinking = FALSE;
7357
7358         if (StrStr(message, "draw")) {
7359             /* Program doesn't have "draw" command */
7360             cps->sendDrawOffers = 0;
7361             return;
7362         }
7363         if (cps->sendTime != 1 &&
7364             (StrStr(message, "time") || StrStr(message, "otim"))) {
7365           /* Program apparently doesn't have "time" or "otim" command */
7366           cps->sendTime = 0;
7367           return;
7368         }
7369         if (StrStr(message, "analyze")) {
7370             cps->analysisSupport = FALSE;
7371             cps->analyzing = FALSE;
7372             Reset(FALSE, TRUE);
7373             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7374             DisplayError(buf2, 0);
7375             return;
7376         }
7377         if (StrStr(message, "(no matching move)st")) {
7378           /* Special kludge for GNU Chess 4 only */
7379           cps->stKludge = TRUE;
7380           SendTimeControl(cps, movesPerSession, timeControl,
7381                           timeIncrement, appData.searchDepth,
7382                           searchTime);
7383           return;
7384         }
7385         if (StrStr(message, "(no matching move)sd")) {
7386           /* Special kludge for GNU Chess 4 only */
7387           cps->sdKludge = TRUE;
7388           SendTimeControl(cps, movesPerSession, timeControl,
7389                           timeIncrement, appData.searchDepth,
7390                           searchTime);
7391           return;
7392         }
7393         if (!StrStr(message, "llegal")) {
7394             return;
7395         }
7396         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7397             gameMode == IcsIdle) return;
7398         if (forwardMostMove <= backwardMostMove) return;
7399         if (pausing) PauseEvent();
7400       if(appData.forceIllegal) {
7401             // [HGM] illegal: machine refused move; force position after move into it
7402           SendToProgram("force\n", cps);
7403           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7404                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7405                 // when black is to move, while there might be nothing on a2 or black
7406                 // might already have the move. So send the board as if white has the move.
7407                 // But first we must change the stm of the engine, as it refused the last move
7408                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7409                 if(WhiteOnMove(forwardMostMove)) {
7410                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7411                     SendBoard(cps, forwardMostMove); // kludgeless board
7412                 } else {
7413                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7414                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7415                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7416                 }
7417           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7418             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7419                  gameMode == TwoMachinesPlay)
7420               SendToProgram("go\n", cps);
7421             return;
7422       } else
7423         if (gameMode == PlayFromGameFile) {
7424             /* Stop reading this game file */
7425             gameMode = EditGame;
7426             ModeHighlight();
7427         }
7428         currentMove = forwardMostMove-1;
7429         DisplayMove(currentMove-1); /* before DisplayMoveError */
7430         SwitchClocks(forwardMostMove-1); // [HGM] race
7431         DisplayBothClocks();
7432         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7433                 parseList[currentMove], cps->which);
7434         DisplayMoveError(buf1);
7435         DrawPosition(FALSE, boards[currentMove]);
7436
7437         /* [HGM] illegal-move claim should forfeit game when Xboard */
7438         /* only passes fully legal moves                            */
7439         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7440             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7441                                 "False illegal-move claim", GE_XBOARD );
7442         }
7443         return;
7444     }
7445     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7446         /* Program has a broken "time" command that
7447            outputs a string not ending in newline.
7448            Don't use it. */
7449         cps->sendTime = 0;
7450     }
7451     
7452     /*
7453      * If chess program startup fails, exit with an error message.
7454      * Attempts to recover here are futile.
7455      */
7456     if ((StrStr(message, "unknown host") != NULL)
7457         || (StrStr(message, "No remote directory") != NULL)
7458         || (StrStr(message, "not found") != NULL)
7459         || (StrStr(message, "No such file") != NULL)
7460         || (StrStr(message, "can't alloc") != NULL)
7461         || (StrStr(message, "Permission denied") != NULL)) {
7462
7463         cps->maybeThinking = FALSE;
7464         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7465                 cps->which, cps->program, cps->host, message);
7466         RemoveInputSource(cps->isr);
7467         DisplayFatalError(buf1, 0, 1);
7468         return;
7469     }
7470     
7471     /* 
7472      * Look for hint output
7473      */
7474     if (sscanf(message, "Hint: %s", buf1) == 1) {
7475         if (cps == &first && hintRequested) {
7476             hintRequested = FALSE;
7477             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7478                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7479                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7480                                     PosFlags(forwardMostMove),
7481                                     fromY, fromX, toY, toX, promoChar, buf1);
7482                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7483                 DisplayInformation(buf2);
7484             } else {
7485                 /* Hint move could not be parsed!? */
7486               snprintf(buf2, sizeof(buf2),
7487                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7488                         buf1, cps->which);
7489                 DisplayError(buf2, 0);
7490             }
7491         } else {
7492             strcpy(lastHint, buf1);
7493         }
7494         return;
7495     }
7496
7497     /*
7498      * Ignore other messages if game is not in progress
7499      */
7500     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7501         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7502
7503     /*
7504      * look for win, lose, draw, or draw offer
7505      */
7506     if (strncmp(message, "1-0", 3) == 0) {
7507         char *p, *q, *r = "";
7508         p = strchr(message, '{');
7509         if (p) {
7510             q = strchr(p, '}');
7511             if (q) {
7512                 *q = NULLCHAR;
7513                 r = p + 1;
7514             }
7515         }
7516         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7517         return;
7518     } else if (strncmp(message, "0-1", 3) == 0) {
7519         char *p, *q, *r = "";
7520         p = strchr(message, '{');
7521         if (p) {
7522             q = strchr(p, '}');
7523             if (q) {
7524                 *q = NULLCHAR;
7525                 r = p + 1;
7526             }
7527         }
7528         /* Kludge for Arasan 4.1 bug */
7529         if (strcmp(r, "Black resigns") == 0) {
7530             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7531             return;
7532         }
7533         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7534         return;
7535     } else if (strncmp(message, "1/2", 3) == 0) {
7536         char *p, *q, *r = "";
7537         p = strchr(message, '{');
7538         if (p) {
7539             q = strchr(p, '}');
7540             if (q) {
7541                 *q = NULLCHAR;
7542                 r = p + 1;
7543             }
7544         }
7545             
7546         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7547         return;
7548
7549     } else if (strncmp(message, "White resign", 12) == 0) {
7550         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7551         return;
7552     } else if (strncmp(message, "Black resign", 12) == 0) {
7553         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7554         return;
7555     } else if (strncmp(message, "White matches", 13) == 0 ||
7556                strncmp(message, "Black matches", 13) == 0   ) {
7557         /* [HGM] ignore GNUShogi noises */
7558         return;
7559     } else if (strncmp(message, "White", 5) == 0 &&
7560                message[5] != '(' &&
7561                StrStr(message, "Black") == NULL) {
7562         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7563         return;
7564     } else if (strncmp(message, "Black", 5) == 0 &&
7565                message[5] != '(') {
7566         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7567         return;
7568     } else if (strcmp(message, "resign") == 0 ||
7569                strcmp(message, "computer resigns") == 0) {
7570         switch (gameMode) {
7571           case MachinePlaysBlack:
7572           case IcsPlayingBlack:
7573             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7574             break;
7575           case MachinePlaysWhite:
7576           case IcsPlayingWhite:
7577             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7578             break;
7579           case TwoMachinesPlay:
7580             if (cps->twoMachinesColor[0] == 'w')
7581               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7582             else
7583               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7584             break;
7585           default:
7586             /* can't happen */
7587             break;
7588         }
7589         return;
7590     } else if (strncmp(message, "opponent mates", 14) == 0) {
7591         switch (gameMode) {
7592           case MachinePlaysBlack:
7593           case IcsPlayingBlack:
7594             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7595             break;
7596           case MachinePlaysWhite:
7597           case IcsPlayingWhite:
7598             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7599             break;
7600           case TwoMachinesPlay:
7601             if (cps->twoMachinesColor[0] == 'w')
7602               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7603             else
7604               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7605             break;
7606           default:
7607             /* can't happen */
7608             break;
7609         }
7610         return;
7611     } else if (strncmp(message, "computer mates", 14) == 0) {
7612         switch (gameMode) {
7613           case MachinePlaysBlack:
7614           case IcsPlayingBlack:
7615             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7616             break;
7617           case MachinePlaysWhite:
7618           case IcsPlayingWhite:
7619             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7620             break;
7621           case TwoMachinesPlay:
7622             if (cps->twoMachinesColor[0] == 'w')
7623               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7624             else
7625               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7626             break;
7627           default:
7628             /* can't happen */
7629             break;
7630         }
7631         return;
7632     } else if (strncmp(message, "checkmate", 9) == 0) {
7633         if (WhiteOnMove(forwardMostMove)) {
7634             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7635         } else {
7636             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7637         }
7638         return;
7639     } else if (strstr(message, "Draw") != NULL ||
7640                strstr(message, "game is a draw") != NULL) {
7641         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7642         return;
7643     } else if (strstr(message, "offer") != NULL &&
7644                strstr(message, "draw") != NULL) {
7645 #if ZIPPY
7646         if (appData.zippyPlay && first.initDone) {
7647             /* Relay offer to ICS */
7648             SendToICS(ics_prefix);
7649             SendToICS("draw\n");
7650         }
7651 #endif
7652         cps->offeredDraw = 2; /* valid until this engine moves twice */
7653         if (gameMode == TwoMachinesPlay) {
7654             if (cps->other->offeredDraw) {
7655                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7656             /* [HGM] in two-machine mode we delay relaying draw offer      */
7657             /* until after we also have move, to see if it is really claim */
7658             }
7659         } else if (gameMode == MachinePlaysWhite ||
7660                    gameMode == MachinePlaysBlack) {
7661           if (userOfferedDraw) {
7662             DisplayInformation(_("Machine accepts your draw offer"));
7663             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7664           } else {
7665             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7666           }
7667         }
7668     }
7669
7670     
7671     /*
7672      * Look for thinking output
7673      */
7674     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7675           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7676                                 ) {
7677         int plylev, mvleft, mvtot, curscore, time;
7678         char mvname[MOVE_LEN];
7679         u64 nodes; // [DM]
7680         char plyext;
7681         int ignore = FALSE;
7682         int prefixHint = FALSE;
7683         mvname[0] = NULLCHAR;
7684
7685         switch (gameMode) {
7686           case MachinePlaysBlack:
7687           case IcsPlayingBlack:
7688             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7689             break;
7690           case MachinePlaysWhite:
7691           case IcsPlayingWhite:
7692             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7693             break;
7694           case AnalyzeMode:
7695           case AnalyzeFile:
7696             break;
7697           case IcsObserving: /* [DM] icsEngineAnalyze */
7698             if (!appData.icsEngineAnalyze) ignore = TRUE;
7699             break;
7700           case TwoMachinesPlay:
7701             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7702                 ignore = TRUE;
7703             }
7704             break;
7705           default:
7706             ignore = TRUE;
7707             break;
7708         }
7709
7710         if (!ignore) {
7711             buf1[0] = NULLCHAR;
7712             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7713                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7714
7715                 if (plyext != ' ' && plyext != '\t') {
7716                     time *= 100;
7717                 }
7718
7719                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7720                 if( cps->scoreIsAbsolute && 
7721                     ( gameMode == MachinePlaysBlack ||
7722                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7723                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7724                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7725                      !WhiteOnMove(currentMove)
7726                     ) )
7727                 {
7728                     curscore = -curscore;
7729                 }
7730
7731
7732                 programStats.depth = plylev;
7733                 programStats.nodes = nodes;
7734                 programStats.time = time;
7735                 programStats.score = curscore;
7736                 programStats.got_only_move = 0;
7737
7738                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7739                         int ticklen;
7740
7741                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7742                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7743                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7744                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7745                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7746                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7747                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7748                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7749                 }
7750
7751                 /* Buffer overflow protection */
7752                 if (buf1[0] != NULLCHAR) {
7753                     if (strlen(buf1) >= sizeof(programStats.movelist)
7754                         && appData.debugMode) {
7755                         fprintf(debugFP,
7756                                 "PV is too long; using the first %u bytes.\n",
7757                                 (unsigned) sizeof(programStats.movelist) - 1);
7758                     }
7759
7760                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7761                 } else {
7762                     sprintf(programStats.movelist, " no PV\n");
7763                 }
7764
7765                 if (programStats.seen_stat) {
7766                     programStats.ok_to_send = 1;
7767                 }
7768
7769                 if (strchr(programStats.movelist, '(') != NULL) {
7770                     programStats.line_is_book = 1;
7771                     programStats.nr_moves = 0;
7772                     programStats.moves_left = 0;
7773                 } else {
7774                     programStats.line_is_book = 0;
7775                 }
7776
7777                 SendProgramStatsToFrontend( cps, &programStats );
7778
7779                 /* 
7780                     [AS] Protect the thinkOutput buffer from overflow... this
7781                     is only useful if buf1 hasn't overflowed first!
7782                 */
7783                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7784                         plylev, 
7785                         (gameMode == TwoMachinesPlay ?
7786                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7787                         ((double) curscore) / 100.0,
7788                         prefixHint ? lastHint : "",
7789                         prefixHint ? " " : "" );
7790
7791                 if( buf1[0] != NULLCHAR ) {
7792                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7793
7794                     if( strlen(buf1) > max_len ) {
7795                         if( appData.debugMode) {
7796                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7797                         }
7798                         buf1[max_len+1] = '\0';
7799                     }
7800
7801                     strcat( thinkOutput, buf1 );
7802                 }
7803
7804                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7805                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7806                     DisplayMove(currentMove - 1);
7807                 }
7808                 return;
7809
7810             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7811                 /* crafty (9.25+) says "(only move) <move>"
7812                  * if there is only 1 legal move
7813                  */
7814                 sscanf(p, "(only move) %s", buf1);
7815                 sprintf(thinkOutput, "%s (only move)", buf1);
7816                 sprintf(programStats.movelist, "%s (only move)", buf1);
7817                 programStats.depth = 1;
7818                 programStats.nr_moves = 1;
7819                 programStats.moves_left = 1;
7820                 programStats.nodes = 1;
7821                 programStats.time = 1;
7822                 programStats.got_only_move = 1;
7823
7824                 /* Not really, but we also use this member to
7825                    mean "line isn't going to change" (Crafty
7826                    isn't searching, so stats won't change) */
7827                 programStats.line_is_book = 1;
7828
7829                 SendProgramStatsToFrontend( cps, &programStats );
7830                 
7831                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7832                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7833                     DisplayMove(currentMove - 1);
7834                 }
7835                 return;
7836             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7837                               &time, &nodes, &plylev, &mvleft,
7838                               &mvtot, mvname) >= 5) {
7839                 /* The stat01: line is from Crafty (9.29+) in response
7840                    to the "." command */
7841                 programStats.seen_stat = 1;
7842                 cps->maybeThinking = TRUE;
7843
7844                 if (programStats.got_only_move || !appData.periodicUpdates)
7845                   return;
7846
7847                 programStats.depth = plylev;
7848                 programStats.time = time;
7849                 programStats.nodes = nodes;
7850                 programStats.moves_left = mvleft;
7851                 programStats.nr_moves = mvtot;
7852                 strcpy(programStats.move_name, mvname);
7853                 programStats.ok_to_send = 1;
7854                 programStats.movelist[0] = '\0';
7855
7856                 SendProgramStatsToFrontend( cps, &programStats );
7857
7858                 return;
7859
7860             } else if (strncmp(message,"++",2) == 0) {
7861                 /* Crafty 9.29+ outputs this */
7862                 programStats.got_fail = 2;
7863                 return;
7864
7865             } else if (strncmp(message,"--",2) == 0) {
7866                 /* Crafty 9.29+ outputs this */
7867                 programStats.got_fail = 1;
7868                 return;
7869
7870             } else if (thinkOutput[0] != NULLCHAR &&
7871                        strncmp(message, "    ", 4) == 0) {
7872                 unsigned message_len;
7873
7874                 p = message;
7875                 while (*p && *p == ' ') p++;
7876
7877                 message_len = strlen( p );
7878
7879                 /* [AS] Avoid buffer overflow */
7880                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7881                     strcat(thinkOutput, " ");
7882                     strcat(thinkOutput, p);
7883                 }
7884
7885                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7886                     strcat(programStats.movelist, " ");
7887                     strcat(programStats.movelist, p);
7888                 }
7889
7890                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7891                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7892                     DisplayMove(currentMove - 1);
7893                 }
7894                 return;
7895             }
7896         }
7897         else {
7898             buf1[0] = NULLCHAR;
7899
7900             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7901                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7902             {
7903                 ChessProgramStats cpstats;
7904
7905                 if (plyext != ' ' && plyext != '\t') {
7906                     time *= 100;
7907                 }
7908
7909                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7910                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7911                     curscore = -curscore;
7912                 }
7913
7914                 cpstats.depth = plylev;
7915                 cpstats.nodes = nodes;
7916                 cpstats.time = time;
7917                 cpstats.score = curscore;
7918                 cpstats.got_only_move = 0;
7919                 cpstats.movelist[0] = '\0';
7920
7921                 if (buf1[0] != NULLCHAR) {
7922                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7923                 }
7924
7925                 cpstats.ok_to_send = 0;
7926                 cpstats.line_is_book = 0;
7927                 cpstats.nr_moves = 0;
7928                 cpstats.moves_left = 0;
7929
7930                 SendProgramStatsToFrontend( cps, &cpstats );
7931             }
7932         }
7933     }
7934 }
7935
7936
7937 /* Parse a game score from the character string "game", and
7938    record it as the history of the current game.  The game
7939    score is NOT assumed to start from the standard position. 
7940    The display is not updated in any way.
7941    */
7942 void
7943 ParseGameHistory(game)
7944      char *game;
7945 {
7946     ChessMove moveType;
7947     int fromX, fromY, toX, toY, boardIndex;
7948     char promoChar;
7949     char *p, *q;
7950     char buf[MSG_SIZ];
7951
7952     if (appData.debugMode)
7953       fprintf(debugFP, "Parsing game history: %s\n", game);
7954
7955     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7956     gameInfo.site = StrSave(appData.icsHost);
7957     gameInfo.date = PGNDate();
7958     gameInfo.round = StrSave("-");
7959
7960     /* Parse out names of players */
7961     while (*game == ' ') game++;
7962     p = buf;
7963     while (*game != ' ') *p++ = *game++;
7964     *p = NULLCHAR;
7965     gameInfo.white = StrSave(buf);
7966     while (*game == ' ') game++;
7967     p = buf;
7968     while (*game != ' ' && *game != '\n') *p++ = *game++;
7969     *p = NULLCHAR;
7970     gameInfo.black = StrSave(buf);
7971
7972     /* Parse moves */
7973     boardIndex = blackPlaysFirst ? 1 : 0;
7974     yynewstr(game);
7975     for (;;) {
7976         yyboardindex = boardIndex;
7977         moveType = (ChessMove) yylex();
7978         switch (moveType) {
7979           case IllegalMove:             /* maybe suicide chess, etc. */
7980   if (appData.debugMode) {
7981     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7982     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7983     setbuf(debugFP, NULL);
7984   }
7985           case WhitePromotionChancellor:
7986           case BlackPromotionChancellor:
7987           case WhitePromotionArchbishop:
7988           case BlackPromotionArchbishop:
7989           case WhitePromotionQueen:
7990           case BlackPromotionQueen:
7991           case WhitePromotionRook:
7992           case BlackPromotionRook:
7993           case WhitePromotionBishop:
7994           case BlackPromotionBishop:
7995           case WhitePromotionKnight:
7996           case BlackPromotionKnight:
7997           case WhitePromotionKing:
7998           case BlackPromotionKing:
7999           case NormalMove:
8000           case WhiteCapturesEnPassant:
8001           case BlackCapturesEnPassant:
8002           case WhiteKingSideCastle:
8003           case WhiteQueenSideCastle:
8004           case BlackKingSideCastle:
8005           case BlackQueenSideCastle:
8006           case WhiteKingSideCastleWild:
8007           case WhiteQueenSideCastleWild:
8008           case BlackKingSideCastleWild:
8009           case BlackQueenSideCastleWild:
8010           /* PUSH Fabien */
8011           case WhiteHSideCastleFR:
8012           case WhiteASideCastleFR:
8013           case BlackHSideCastleFR:
8014           case BlackASideCastleFR:
8015           /* POP Fabien */
8016             fromX = currentMoveString[0] - AAA;
8017             fromY = currentMoveString[1] - ONE;
8018             toX = currentMoveString[2] - AAA;
8019             toY = currentMoveString[3] - ONE;
8020             promoChar = currentMoveString[4];
8021             break;
8022           case WhiteDrop:
8023           case BlackDrop:
8024             fromX = moveType == WhiteDrop ?
8025               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8026             (int) CharToPiece(ToLower(currentMoveString[0]));
8027             fromY = DROP_RANK;
8028             toX = currentMoveString[2] - AAA;
8029             toY = currentMoveString[3] - ONE;
8030             promoChar = NULLCHAR;
8031             break;
8032           case AmbiguousMove:
8033             /* bug? */
8034             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8035   if (appData.debugMode) {
8036     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8037     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8038     setbuf(debugFP, NULL);
8039   }
8040             DisplayError(buf, 0);
8041             return;
8042           case ImpossibleMove:
8043             /* bug? */
8044             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8045   if (appData.debugMode) {
8046     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8047     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8048     setbuf(debugFP, NULL);
8049   }
8050             DisplayError(buf, 0);
8051             return;
8052           case (ChessMove) 0:   /* end of file */
8053             if (boardIndex < backwardMostMove) {
8054                 /* Oops, gap.  How did that happen? */
8055                 DisplayError(_("Gap in move list"), 0);
8056                 return;
8057             }
8058             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8059             if (boardIndex > forwardMostMove) {
8060                 forwardMostMove = boardIndex;
8061             }
8062             return;
8063           case ElapsedTime:
8064             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8065                 strcat(parseList[boardIndex-1], " ");
8066                 strcat(parseList[boardIndex-1], yy_text);
8067             }
8068             continue;
8069           case Comment:
8070           case PGNTag:
8071           case NAG:
8072           default:
8073             /* ignore */
8074             continue;
8075           case WhiteWins:
8076           case BlackWins:
8077           case GameIsDrawn:
8078           case GameUnfinished:
8079             if (gameMode == IcsExamining) {
8080                 if (boardIndex < backwardMostMove) {
8081                     /* Oops, gap.  How did that happen? */
8082                     return;
8083                 }
8084                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8085                 return;
8086             }
8087             gameInfo.result = moveType;
8088             p = strchr(yy_text, '{');
8089             if (p == NULL) p = strchr(yy_text, '(');
8090             if (p == NULL) {
8091                 p = yy_text;
8092                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8093             } else {
8094                 q = strchr(p, *p == '{' ? '}' : ')');
8095                 if (q != NULL) *q = NULLCHAR;
8096                 p++;
8097             }
8098             gameInfo.resultDetails = StrSave(p);
8099             continue;
8100         }
8101         if (boardIndex >= forwardMostMove &&
8102             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8103             backwardMostMove = blackPlaysFirst ? 1 : 0;
8104             return;
8105         }
8106         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8107                                  fromY, fromX, toY, toX, promoChar,
8108                                  parseList[boardIndex]);
8109         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8110         /* currentMoveString is set as a side-effect of yylex */
8111         strcpy(moveList[boardIndex], currentMoveString);
8112         strcat(moveList[boardIndex], "\n");
8113         boardIndex++;
8114         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8115         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8116           case MT_NONE:
8117           case MT_STALEMATE:
8118           default:
8119             break;
8120           case MT_CHECK:
8121             if(gameInfo.variant != VariantShogi)
8122                 strcat(parseList[boardIndex - 1], "+");
8123             break;
8124           case MT_CHECKMATE:
8125           case MT_STAINMATE:
8126             strcat(parseList[boardIndex - 1], "#");
8127             break;
8128         }
8129     }
8130 }
8131
8132
8133 /* Apply a move to the given board  */
8134 void
8135 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8136      int fromX, fromY, toX, toY;
8137      int promoChar;
8138      Board board;
8139 {
8140   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8141   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8142
8143     /* [HGM] compute & store e.p. status and castling rights for new position */
8144     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8145     { int i;
8146
8147       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8148       oldEP = (signed char)board[EP_STATUS];
8149       board[EP_STATUS] = EP_NONE;
8150
8151       if( board[toY][toX] != EmptySquare ) 
8152            board[EP_STATUS] = EP_CAPTURE;  
8153
8154       if( board[fromY][fromX] == WhitePawn ) {
8155            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8156                board[EP_STATUS] = EP_PAWN_MOVE;
8157            if( toY-fromY==2) {
8158                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8159                         gameInfo.variant != VariantBerolina || toX < fromX)
8160                       board[EP_STATUS] = toX | berolina;
8161                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8162                         gameInfo.variant != VariantBerolina || toX > fromX) 
8163                       board[EP_STATUS] = toX;
8164            }
8165       } else 
8166       if( board[fromY][fromX] == BlackPawn ) {
8167            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8168                board[EP_STATUS] = EP_PAWN_MOVE; 
8169            if( toY-fromY== -2) {
8170                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8171                         gameInfo.variant != VariantBerolina || toX < fromX)
8172                       board[EP_STATUS] = toX | berolina;
8173                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8174                         gameInfo.variant != VariantBerolina || toX > fromX) 
8175                       board[EP_STATUS] = toX;
8176            }
8177        }
8178
8179        for(i=0; i<nrCastlingRights; i++) {
8180            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8181               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8182              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8183        }
8184
8185     }
8186
8187   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8188   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8189        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8190          
8191   if (fromX == toX && fromY == toY) return;
8192
8193   if (fromY == DROP_RANK) {
8194         /* must be first */
8195         piece = board[toY][toX] = (ChessSquare) fromX;
8196   } else {
8197      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8198      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8199      if(gameInfo.variant == VariantKnightmate)
8200          king += (int) WhiteUnicorn - (int) WhiteKing;
8201
8202     /* Code added by Tord: */
8203     /* FRC castling assumed when king captures friendly rook. */
8204     if (board[fromY][fromX] == WhiteKing &&
8205              board[toY][toX] == WhiteRook) {
8206       board[fromY][fromX] = EmptySquare;
8207       board[toY][toX] = EmptySquare;
8208       if(toX > fromX) {
8209         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8210       } else {
8211         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8212       }
8213     } else if (board[fromY][fromX] == BlackKing &&
8214                board[toY][toX] == BlackRook) {
8215       board[fromY][fromX] = EmptySquare;
8216       board[toY][toX] = EmptySquare;
8217       if(toX > fromX) {
8218         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8219       } else {
8220         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8221       }
8222     /* End of code added by Tord */
8223
8224     } else if (board[fromY][fromX] == king
8225         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8226         && toY == fromY && toX > fromX+1) {
8227         board[fromY][fromX] = EmptySquare;
8228         board[toY][toX] = king;
8229         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8230         board[fromY][BOARD_RGHT-1] = EmptySquare;
8231     } else if (board[fromY][fromX] == king
8232         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8233                && toY == fromY && toX < fromX-1) {
8234         board[fromY][fromX] = EmptySquare;
8235         board[toY][toX] = king;
8236         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8237         board[fromY][BOARD_LEFT] = EmptySquare;
8238     } else if (board[fromY][fromX] == WhitePawn
8239                && toY >= BOARD_HEIGHT-promoRank
8240                && gameInfo.variant != VariantXiangqi
8241                ) {
8242         /* white pawn promotion */
8243         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8244         if (board[toY][toX] == EmptySquare) {
8245             board[toY][toX] = WhiteQueen;
8246         }
8247         if(gameInfo.variant==VariantBughouse ||
8248            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8249             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8250         board[fromY][fromX] = EmptySquare;
8251     } else if ((fromY == BOARD_HEIGHT-4)
8252                && (toX != fromX)
8253                && gameInfo.variant != VariantXiangqi
8254                && gameInfo.variant != VariantBerolina
8255                && (board[fromY][fromX] == WhitePawn)
8256                && (board[toY][toX] == EmptySquare)) {
8257         board[fromY][fromX] = EmptySquare;
8258         board[toY][toX] = WhitePawn;
8259         captured = board[toY - 1][toX];
8260         board[toY - 1][toX] = EmptySquare;
8261     } else if ((fromY == BOARD_HEIGHT-4)
8262                && (toX == fromX)
8263                && gameInfo.variant == VariantBerolina
8264                && (board[fromY][fromX] == WhitePawn)
8265                && (board[toY][toX] == EmptySquare)) {
8266         board[fromY][fromX] = EmptySquare;
8267         board[toY][toX] = WhitePawn;
8268         if(oldEP & EP_BEROLIN_A) {
8269                 captured = board[fromY][fromX-1];
8270                 board[fromY][fromX-1] = EmptySquare;
8271         }else{  captured = board[fromY][fromX+1];
8272                 board[fromY][fromX+1] = EmptySquare;
8273         }
8274     } else if (board[fromY][fromX] == king
8275         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8276                && toY == fromY && toX > fromX+1) {
8277         board[fromY][fromX] = EmptySquare;
8278         board[toY][toX] = king;
8279         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8280         board[fromY][BOARD_RGHT-1] = EmptySquare;
8281     } else if (board[fromY][fromX] == king
8282         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8283                && toY == fromY && toX < fromX-1) {
8284         board[fromY][fromX] = EmptySquare;
8285         board[toY][toX] = king;
8286         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8287         board[fromY][BOARD_LEFT] = EmptySquare;
8288     } else if (fromY == 7 && fromX == 3
8289                && board[fromY][fromX] == BlackKing
8290                && toY == 7 && toX == 5) {
8291         board[fromY][fromX] = EmptySquare;
8292         board[toY][toX] = BlackKing;
8293         board[fromY][7] = EmptySquare;
8294         board[toY][4] = BlackRook;
8295     } else if (fromY == 7 && fromX == 3
8296                && board[fromY][fromX] == BlackKing
8297                && toY == 7 && toX == 1) {
8298         board[fromY][fromX] = EmptySquare;
8299         board[toY][toX] = BlackKing;
8300         board[fromY][0] = EmptySquare;
8301         board[toY][2] = BlackRook;
8302     } else if (board[fromY][fromX] == BlackPawn
8303                && toY < promoRank
8304                && gameInfo.variant != VariantXiangqi
8305                ) {
8306         /* black pawn promotion */
8307         board[toY][toX] = CharToPiece(ToLower(promoChar));
8308         if (board[toY][toX] == EmptySquare) {
8309             board[toY][toX] = BlackQueen;
8310         }
8311         if(gameInfo.variant==VariantBughouse ||
8312            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8313             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8314         board[fromY][fromX] = EmptySquare;
8315     } else if ((fromY == 3)
8316                && (toX != fromX)
8317                && gameInfo.variant != VariantXiangqi
8318                && gameInfo.variant != VariantBerolina
8319                && (board[fromY][fromX] == BlackPawn)
8320                && (board[toY][toX] == EmptySquare)) {
8321         board[fromY][fromX] = EmptySquare;
8322         board[toY][toX] = BlackPawn;
8323         captured = board[toY + 1][toX];
8324         board[toY + 1][toX] = EmptySquare;
8325     } else if ((fromY == 3)
8326                && (toX == fromX)
8327                && gameInfo.variant == VariantBerolina
8328                && (board[fromY][fromX] == BlackPawn)
8329                && (board[toY][toX] == EmptySquare)) {
8330         board[fromY][fromX] = EmptySquare;
8331         board[toY][toX] = BlackPawn;
8332         if(oldEP & EP_BEROLIN_A) {
8333                 captured = board[fromY][fromX-1];
8334                 board[fromY][fromX-1] = EmptySquare;
8335         }else{  captured = board[fromY][fromX+1];
8336                 board[fromY][fromX+1] = EmptySquare;
8337         }
8338     } else {
8339         board[toY][toX] = board[fromY][fromX];
8340         board[fromY][fromX] = EmptySquare;
8341     }
8342
8343     /* [HGM] now we promote for Shogi, if needed */
8344     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8345         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8346   }
8347
8348     if (gameInfo.holdingsWidth != 0) {
8349
8350       /* !!A lot more code needs to be written to support holdings  */
8351       /* [HGM] OK, so I have written it. Holdings are stored in the */
8352       /* penultimate board files, so they are automaticlly stored   */
8353       /* in the game history.                                       */
8354       if (fromY == DROP_RANK) {
8355         /* Delete from holdings, by decreasing count */
8356         /* and erasing image if necessary            */
8357         p = (int) fromX;
8358         if(p < (int) BlackPawn) { /* white drop */
8359              p -= (int)WhitePawn;
8360                  p = PieceToNumber((ChessSquare)p);
8361              if(p >= gameInfo.holdingsSize) p = 0;
8362              if(--board[p][BOARD_WIDTH-2] <= 0)
8363                   board[p][BOARD_WIDTH-1] = EmptySquare;
8364              if((int)board[p][BOARD_WIDTH-2] < 0)
8365                         board[p][BOARD_WIDTH-2] = 0;
8366         } else {                  /* black drop */
8367              p -= (int)BlackPawn;
8368                  p = PieceToNumber((ChessSquare)p);
8369              if(p >= gameInfo.holdingsSize) p = 0;
8370              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8371                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8372              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8373                         board[BOARD_HEIGHT-1-p][1] = 0;
8374         }
8375       }
8376       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8377           && gameInfo.variant != VariantBughouse        ) {
8378         /* [HGM] holdings: Add to holdings, if holdings exist */
8379         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8380                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8381                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8382         }
8383         p = (int) captured;
8384         if (p >= (int) BlackPawn) {
8385           p -= (int)BlackPawn;
8386           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8387                   /* in Shogi restore piece to its original  first */
8388                   captured = (ChessSquare) (DEMOTED captured);
8389                   p = DEMOTED p;
8390           }
8391           p = PieceToNumber((ChessSquare)p);
8392           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8393           board[p][BOARD_WIDTH-2]++;
8394           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8395         } else {
8396           p -= (int)WhitePawn;
8397           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8398                   captured = (ChessSquare) (DEMOTED captured);
8399                   p = DEMOTED p;
8400           }
8401           p = PieceToNumber((ChessSquare)p);
8402           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8403           board[BOARD_HEIGHT-1-p][1]++;
8404           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8405         }
8406       }
8407     } else if (gameInfo.variant == VariantAtomic) {
8408       if (captured != EmptySquare) {
8409         int y, x;
8410         for (y = toY-1; y <= toY+1; y++) {
8411           for (x = toX-1; x <= toX+1; x++) {
8412             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8413                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8414               board[y][x] = EmptySquare;
8415             }
8416           }
8417         }
8418         board[toY][toX] = EmptySquare;
8419       }
8420     }
8421     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8422         /* [HGM] Shogi promotions */
8423         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8424     }
8425
8426     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8427                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8428         // [HGM] superchess: take promotion piece out of holdings
8429         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8430         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8431             if(!--board[k][BOARD_WIDTH-2])
8432                 board[k][BOARD_WIDTH-1] = EmptySquare;
8433         } else {
8434             if(!--board[BOARD_HEIGHT-1-k][1])
8435                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8436         }
8437     }
8438
8439 }
8440
8441 /* Updates forwardMostMove */
8442 void
8443 MakeMove(fromX, fromY, toX, toY, promoChar)
8444      int fromX, fromY, toX, toY;
8445      int promoChar;
8446 {
8447 //    forwardMostMove++; // [HGM] bare: moved downstream
8448
8449     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8450         int timeLeft; static int lastLoadFlag=0; int king, piece;
8451         piece = boards[forwardMostMove][fromY][fromX];
8452         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8453         if(gameInfo.variant == VariantKnightmate)
8454             king += (int) WhiteUnicorn - (int) WhiteKing;
8455         if(forwardMostMove == 0) {
8456             if(blackPlaysFirst) 
8457                 fprintf(serverMoves, "%s;", second.tidy);
8458             fprintf(serverMoves, "%s;", first.tidy);
8459             if(!blackPlaysFirst) 
8460                 fprintf(serverMoves, "%s;", second.tidy);
8461         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8462         lastLoadFlag = loadFlag;
8463         // print base move
8464         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8465         // print castling suffix
8466         if( toY == fromY && piece == king ) {
8467             if(toX-fromX > 1)
8468                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8469             if(fromX-toX >1)
8470                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8471         }
8472         // e.p. suffix
8473         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8474              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8475              boards[forwardMostMove][toY][toX] == EmptySquare
8476              && fromX != toX )
8477                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8478         // promotion suffix
8479         if(promoChar != NULLCHAR)
8480                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8481         if(!loadFlag) {
8482             fprintf(serverMoves, "/%d/%d",
8483                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8484             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8485             else                      timeLeft = blackTimeRemaining/1000;
8486             fprintf(serverMoves, "/%d", timeLeft);
8487         }
8488         fflush(serverMoves);
8489     }
8490
8491     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8492       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8493                         0, 1);
8494       return;
8495     }
8496     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8497     if (commentList[forwardMostMove+1] != NULL) {
8498         free(commentList[forwardMostMove+1]);
8499         commentList[forwardMostMove+1] = NULL;
8500     }
8501     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8502     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8503     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8504     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8505     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8506     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8507     gameInfo.result = GameUnfinished;
8508     if (gameInfo.resultDetails != NULL) {
8509         free(gameInfo.resultDetails);
8510         gameInfo.resultDetails = NULL;
8511     }
8512     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8513                               moveList[forwardMostMove - 1]);
8514     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8515                              PosFlags(forwardMostMove - 1),
8516                              fromY, fromX, toY, toX, promoChar,
8517                              parseList[forwardMostMove - 1]);
8518     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8519       case MT_NONE:
8520       case MT_STALEMATE:
8521       default:
8522         break;
8523       case MT_CHECK:
8524         if(gameInfo.variant != VariantShogi)
8525             strcat(parseList[forwardMostMove - 1], "+");
8526         break;
8527       case MT_CHECKMATE:
8528       case MT_STAINMATE:
8529         strcat(parseList[forwardMostMove - 1], "#");
8530         break;
8531     }
8532     if (appData.debugMode) {
8533         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8534     }
8535
8536 }
8537
8538 /* Updates currentMove if not pausing */
8539 void
8540 ShowMove(fromX, fromY, toX, toY)
8541 {
8542     int instant = (gameMode == PlayFromGameFile) ?
8543         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8544     if(appData.noGUI) return;
8545     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8546         if (!instant) {
8547             if (forwardMostMove == currentMove + 1) {
8548                 AnimateMove(boards[forwardMostMove - 1],
8549                             fromX, fromY, toX, toY);
8550             }
8551             if (appData.highlightLastMove) {
8552                 SetHighlights(fromX, fromY, toX, toY);
8553             }
8554         }
8555         currentMove = forwardMostMove;
8556     }
8557
8558     if (instant) return;
8559
8560     DisplayMove(currentMove - 1);
8561     DrawPosition(FALSE, boards[currentMove]);
8562     DisplayBothClocks();
8563     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8564 }
8565
8566 void SendEgtPath(ChessProgramState *cps)
8567 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8568         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8569
8570         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8571
8572         while(*p) {
8573             char c, *q = name+1, *r, *s;
8574
8575             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8576             while(*p && *p != ',') *q++ = *p++;
8577             *q++ = ':'; *q = 0;
8578             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8579                 strcmp(name, ",nalimov:") == 0 ) {
8580                 // take nalimov path from the menu-changeable option first, if it is defined
8581                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8582                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8583             } else
8584             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8585                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8586                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8587                 s = r = StrStr(s, ":") + 1; // beginning of path info
8588                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8589                 c = *r; *r = 0;             // temporarily null-terminate path info
8590                     *--q = 0;               // strip of trailig ':' from name
8591                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8592                 *r = c;
8593                 SendToProgram(buf,cps);     // send egtbpath command for this format
8594             }
8595             if(*p == ',') p++; // read away comma to position for next format name
8596         }
8597 }
8598
8599 void
8600 InitChessProgram(cps, setup)
8601      ChessProgramState *cps;
8602      int setup; /* [HGM] needed to setup FRC opening position */
8603 {
8604     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8605     if (appData.noChessProgram) return;
8606     hintRequested = FALSE;
8607     bookRequested = FALSE;
8608
8609     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8610     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8611     if(cps->memSize) { /* [HGM] memory */
8612         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8613         SendToProgram(buf, cps);
8614     }
8615     SendEgtPath(cps); /* [HGM] EGT */
8616     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8617         sprintf(buf, "cores %d\n", appData.smpCores);
8618         SendToProgram(buf, cps);
8619     }
8620
8621     SendToProgram(cps->initString, cps);
8622     if (gameInfo.variant != VariantNormal &&
8623         gameInfo.variant != VariantLoadable
8624         /* [HGM] also send variant if board size non-standard */
8625         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8626                                             ) {
8627       char *v = VariantName(gameInfo.variant);
8628       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8629         /* [HGM] in protocol 1 we have to assume all variants valid */
8630         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8631         DisplayFatalError(buf, 0, 1);
8632         return;
8633       }
8634
8635       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8636       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8637       if( gameInfo.variant == VariantXiangqi )
8638            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8639       if( gameInfo.variant == VariantShogi )
8640            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8641       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8642            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8643       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8644                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8645            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8646       if( gameInfo.variant == VariantCourier )
8647            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8648       if( gameInfo.variant == VariantSuper )
8649            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8650       if( gameInfo.variant == VariantGreat )
8651            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8652
8653       if(overruled) {
8654            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8655                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8656            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8657            if(StrStr(cps->variants, b) == NULL) { 
8658                // specific sized variant not known, check if general sizing allowed
8659                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8660                    if(StrStr(cps->variants, "boardsize") == NULL) {
8661                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8662                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8663                        DisplayFatalError(buf, 0, 1);
8664                        return;
8665                    }
8666                    /* [HGM] here we really should compare with the maximum supported board size */
8667                }
8668            }
8669       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8670       sprintf(buf, "variant %s\n", b);
8671       SendToProgram(buf, cps);
8672     }
8673     currentlyInitializedVariant = gameInfo.variant;
8674
8675     /* [HGM] send opening position in FRC to first engine */
8676     if(setup) {
8677           SendToProgram("force\n", cps);
8678           SendBoard(cps, 0);
8679           /* engine is now in force mode! Set flag to wake it up after first move. */
8680           setboardSpoiledMachineBlack = 1;
8681     }
8682
8683     if (cps->sendICS) {
8684       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8685       SendToProgram(buf, cps);
8686     }
8687     cps->maybeThinking = FALSE;
8688     cps->offeredDraw = 0;
8689     if (!appData.icsActive) {
8690         SendTimeControl(cps, movesPerSession, timeControl,
8691                         timeIncrement, appData.searchDepth,
8692                         searchTime);
8693     }
8694     if (appData.showThinking 
8695         // [HGM] thinking: four options require thinking output to be sent
8696         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8697                                 ) {
8698         SendToProgram("post\n", cps);
8699     }
8700     SendToProgram("hard\n", cps);
8701     if (!appData.ponderNextMove) {
8702         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8703            it without being sure what state we are in first.  "hard"
8704            is not a toggle, so that one is OK.
8705          */
8706         SendToProgram("easy\n", cps);
8707     }
8708     if (cps->usePing) {
8709       sprintf(buf, "ping %d\n", ++cps->lastPing);
8710       SendToProgram(buf, cps);
8711     }
8712     cps->initDone = TRUE;
8713 }   
8714
8715
8716 void
8717 StartChessProgram(cps)
8718      ChessProgramState *cps;
8719 {
8720     char buf[MSG_SIZ];
8721     int err;
8722
8723     if (appData.noChessProgram) return;
8724     cps->initDone = FALSE;
8725
8726     if (strcmp(cps->host, "localhost") == 0) {
8727         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8728     } else if (*appData.remoteShell == NULLCHAR) {
8729         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8730     } else {
8731         if (*appData.remoteUser == NULLCHAR) {
8732           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8733                     cps->program);
8734         } else {
8735           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8736                     cps->host, appData.remoteUser, cps->program);
8737         }
8738         err = StartChildProcess(buf, "", &cps->pr);
8739     }
8740     
8741     if (err != 0) {
8742         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8743         DisplayFatalError(buf, err, 1);
8744         cps->pr = NoProc;
8745         cps->isr = NULL;
8746         return;
8747     }
8748     
8749     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8750     if (cps->protocolVersion > 1) {
8751       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8752       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8753       cps->comboCnt = 0;  //                and values of combo boxes
8754       SendToProgram(buf, cps);
8755     } else {
8756       SendToProgram("xboard\n", cps);
8757     }
8758 }
8759
8760
8761 void
8762 TwoMachinesEventIfReady P((void))
8763 {
8764   if (first.lastPing != first.lastPong) {
8765     DisplayMessage("", _("Waiting for first chess program"));
8766     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8767     return;
8768   }
8769   if (second.lastPing != second.lastPong) {
8770     DisplayMessage("", _("Waiting for second chess program"));
8771     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8772     return;
8773   }
8774   ThawUI();
8775   TwoMachinesEvent();
8776 }
8777
8778 void
8779 NextMatchGame P((void))
8780 {
8781     int index; /* [HGM] autoinc: step load index during match */
8782     Reset(FALSE, TRUE);
8783     if (*appData.loadGameFile != NULLCHAR) {
8784         index = appData.loadGameIndex;
8785         if(index < 0) { // [HGM] autoinc
8786             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8787             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8788         } 
8789         LoadGameFromFile(appData.loadGameFile,
8790                          index,
8791                          appData.loadGameFile, FALSE);
8792     } else if (*appData.loadPositionFile != NULLCHAR) {
8793         index = appData.loadPositionIndex;
8794         if(index < 0) { // [HGM] autoinc
8795             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8796             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8797         } 
8798         LoadPositionFromFile(appData.loadPositionFile,
8799                              index,
8800                              appData.loadPositionFile);
8801     }
8802     TwoMachinesEventIfReady();
8803 }
8804
8805 void UserAdjudicationEvent( int result )
8806 {
8807     ChessMove gameResult = GameIsDrawn;
8808
8809     if( result > 0 ) {
8810         gameResult = WhiteWins;
8811     }
8812     else if( result < 0 ) {
8813         gameResult = BlackWins;
8814     }
8815
8816     if( gameMode == TwoMachinesPlay ) {
8817         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8818     }
8819 }
8820
8821
8822 // [HGM] save: calculate checksum of game to make games easily identifiable
8823 int StringCheckSum(char *s)
8824 {
8825         int i = 0;
8826         if(s==NULL) return 0;
8827         while(*s) i = i*259 + *s++;
8828         return i;
8829 }
8830
8831 int GameCheckSum()
8832 {
8833         int i, sum=0;
8834         for(i=backwardMostMove; i<forwardMostMove; i++) {
8835                 sum += pvInfoList[i].depth;
8836                 sum += StringCheckSum(parseList[i]);
8837                 sum += StringCheckSum(commentList[i]);
8838                 sum *= 261;
8839         }
8840         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8841         return sum + StringCheckSum(commentList[i]);
8842 } // end of save patch
8843
8844 void
8845 GameEnds(result, resultDetails, whosays)
8846      ChessMove result;
8847      char *resultDetails;
8848      int whosays;
8849 {
8850     GameMode nextGameMode;
8851     int isIcsGame;
8852     char buf[MSG_SIZ];
8853
8854     if(endingGame) return; /* [HGM] crash: forbid recursion */
8855     endingGame = 1;
8856
8857     if (appData.debugMode) {
8858       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8859               result, resultDetails ? resultDetails : "(null)", whosays);
8860     }
8861
8862     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8863
8864     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8865         /* If we are playing on ICS, the server decides when the
8866            game is over, but the engine can offer to draw, claim 
8867            a draw, or resign. 
8868          */
8869 #if ZIPPY
8870         if (appData.zippyPlay && first.initDone) {
8871             if (result == GameIsDrawn) {
8872                 /* In case draw still needs to be claimed */
8873                 SendToICS(ics_prefix);
8874                 SendToICS("draw\n");
8875             } else if (StrCaseStr(resultDetails, "resign")) {
8876                 SendToICS(ics_prefix);
8877                 SendToICS("resign\n");
8878             }
8879         }
8880 #endif
8881         endingGame = 0; /* [HGM] crash */
8882         return;
8883     }
8884
8885     /* If we're loading the game from a file, stop */
8886     if (whosays == GE_FILE) {
8887       (void) StopLoadGameTimer();
8888       gameFileFP = NULL;
8889     }
8890
8891     /* Cancel draw offers */
8892     first.offeredDraw = second.offeredDraw = 0;
8893
8894     /* If this is an ICS game, only ICS can really say it's done;
8895        if not, anyone can. */
8896     isIcsGame = (gameMode == IcsPlayingWhite || 
8897                  gameMode == IcsPlayingBlack || 
8898                  gameMode == IcsObserving    || 
8899                  gameMode == IcsExamining);
8900
8901     if (!isIcsGame || whosays == GE_ICS) {
8902         /* OK -- not an ICS game, or ICS said it was done */
8903         StopClocks();
8904         if (!isIcsGame && !appData.noChessProgram) 
8905           SetUserThinkingEnables();
8906     
8907         /* [HGM] if a machine claims the game end we verify this claim */
8908         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8909             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8910                 char claimer;
8911                 ChessMove trueResult = (ChessMove) -1;
8912
8913                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8914                                             first.twoMachinesColor[0] :
8915                                             second.twoMachinesColor[0] ;
8916
8917                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8918                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8919                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8920                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8921                 } else
8922                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8923                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8924                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8925                 } else
8926                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8927                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8928                 }
8929
8930                 // now verify win claims, but not in drop games, as we don't understand those yet
8931                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8932                                                  || gameInfo.variant == VariantGreat) &&
8933                     (result == WhiteWins && claimer == 'w' ||
8934                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8935                       if (appData.debugMode) {
8936                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8937                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8938                       }
8939                       if(result != trueResult) {
8940                               sprintf(buf, "False win claim: '%s'", resultDetails);
8941                               result = claimer == 'w' ? BlackWins : WhiteWins;
8942                               resultDetails = buf;
8943                       }
8944                 } else
8945                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8946                     && (forwardMostMove <= backwardMostMove ||
8947                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8948                         (claimer=='b')==(forwardMostMove&1))
8949                                                                                   ) {
8950                       /* [HGM] verify: draws that were not flagged are false claims */
8951                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8952                       result = claimer == 'w' ? BlackWins : WhiteWins;
8953                       resultDetails = buf;
8954                 }
8955                 /* (Claiming a loss is accepted no questions asked!) */
8956             }
8957             /* [HGM] bare: don't allow bare King to win */
8958             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8959                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8960                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8961                && result != GameIsDrawn)
8962             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8963                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8964                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8965                         if(p >= 0 && p <= (int)WhiteKing) k++;
8966                 }
8967                 if (appData.debugMode) {
8968                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8969                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8970                 }
8971                 if(k <= 1) {
8972                         result = GameIsDrawn;
8973                         sprintf(buf, "%s but bare king", resultDetails);
8974                         resultDetails = buf;
8975                 }
8976             }
8977         }
8978
8979
8980         if(serverMoves != NULL && !loadFlag) { char c = '=';
8981             if(result==WhiteWins) c = '+';
8982             if(result==BlackWins) c = '-';
8983             if(resultDetails != NULL)
8984                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8985         }
8986         if (resultDetails != NULL) {
8987             gameInfo.result = result;
8988             gameInfo.resultDetails = StrSave(resultDetails);
8989
8990             /* display last move only if game was not loaded from file */
8991             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8992                 DisplayMove(currentMove - 1);
8993     
8994             if (forwardMostMove != 0) {
8995                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8996                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8997                                                                 ) {
8998                     if (*appData.saveGameFile != NULLCHAR) {
8999                         SaveGameToFile(appData.saveGameFile, TRUE);
9000                     } else if (appData.autoSaveGames) {
9001                         AutoSaveGame();
9002                     }
9003                     if (*appData.savePositionFile != NULLCHAR) {
9004                         SavePositionToFile(appData.savePositionFile);
9005                     }
9006                 }
9007             }
9008
9009             /* Tell program how game ended in case it is learning */
9010             /* [HGM] Moved this to after saving the PGN, just in case */
9011             /* engine died and we got here through time loss. In that */
9012             /* case we will get a fatal error writing the pipe, which */
9013             /* would otherwise lose us the PGN.                       */
9014             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9015             /* output during GameEnds should never be fatal anymore   */
9016             if (gameMode == MachinePlaysWhite ||
9017                 gameMode == MachinePlaysBlack ||
9018                 gameMode == TwoMachinesPlay ||
9019                 gameMode == IcsPlayingWhite ||
9020                 gameMode == IcsPlayingBlack ||
9021                 gameMode == BeginningOfGame) {
9022                 char buf[MSG_SIZ];
9023                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9024                         resultDetails);
9025                 if (first.pr != NoProc) {
9026                     SendToProgram(buf, &first);
9027                 }
9028                 if (second.pr != NoProc &&
9029                     gameMode == TwoMachinesPlay) {
9030                     SendToProgram(buf, &second);
9031                 }
9032             }
9033         }
9034
9035         if (appData.icsActive) {
9036             if (appData.quietPlay &&
9037                 (gameMode == IcsPlayingWhite ||
9038                  gameMode == IcsPlayingBlack)) {
9039                 SendToICS(ics_prefix);
9040                 SendToICS("set shout 1\n");
9041             }
9042             nextGameMode = IcsIdle;
9043             ics_user_moved = FALSE;
9044             /* clean up premove.  It's ugly when the game has ended and the
9045              * premove highlights are still on the board.
9046              */
9047             if (gotPremove) {
9048               gotPremove = FALSE;
9049               ClearPremoveHighlights();
9050               DrawPosition(FALSE, boards[currentMove]);
9051             }
9052             if (whosays == GE_ICS) {
9053                 switch (result) {
9054                 case WhiteWins:
9055                     if (gameMode == IcsPlayingWhite)
9056                         PlayIcsWinSound();
9057                     else if(gameMode == IcsPlayingBlack)
9058                         PlayIcsLossSound();
9059                     break;
9060                 case BlackWins:
9061                     if (gameMode == IcsPlayingBlack)
9062                         PlayIcsWinSound();
9063                     else if(gameMode == IcsPlayingWhite)
9064                         PlayIcsLossSound();
9065                     break;
9066                 case GameIsDrawn:
9067                     PlayIcsDrawSound();
9068                     break;
9069                 default:
9070                     PlayIcsUnfinishedSound();
9071                 }
9072             }
9073         } else if (gameMode == EditGame ||
9074                    gameMode == PlayFromGameFile || 
9075                    gameMode == AnalyzeMode || 
9076                    gameMode == AnalyzeFile) {
9077             nextGameMode = gameMode;
9078         } else {
9079             nextGameMode = EndOfGame;
9080         }
9081         pausing = FALSE;
9082         ModeHighlight();
9083     } else {
9084         nextGameMode = gameMode;
9085     }
9086
9087     if (appData.noChessProgram) {
9088         gameMode = nextGameMode;
9089         ModeHighlight();
9090         endingGame = 0; /* [HGM] crash */
9091         return;
9092     }
9093
9094     if (first.reuse) {
9095         /* Put first chess program into idle state */
9096         if (first.pr != NoProc &&
9097             (gameMode == MachinePlaysWhite ||
9098              gameMode == MachinePlaysBlack ||
9099              gameMode == TwoMachinesPlay ||
9100              gameMode == IcsPlayingWhite ||
9101              gameMode == IcsPlayingBlack ||
9102              gameMode == BeginningOfGame)) {
9103             SendToProgram("force\n", &first);
9104             if (first.usePing) {
9105               char buf[MSG_SIZ];
9106               sprintf(buf, "ping %d\n", ++first.lastPing);
9107               SendToProgram(buf, &first);
9108             }
9109         }
9110     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9111         /* Kill off first chess program */
9112         if (first.isr != NULL)
9113           RemoveInputSource(first.isr);
9114         first.isr = NULL;
9115     
9116         if (first.pr != NoProc) {
9117             ExitAnalyzeMode();
9118             DoSleep( appData.delayBeforeQuit );
9119             SendToProgram("quit\n", &first);
9120             DoSleep( appData.delayAfterQuit );
9121             DestroyChildProcess(first.pr, first.useSigterm);
9122         }
9123         first.pr = NoProc;
9124     }
9125     if (second.reuse) {
9126         /* Put second chess program into idle state */
9127         if (second.pr != NoProc &&
9128             gameMode == TwoMachinesPlay) {
9129             SendToProgram("force\n", &second);
9130             if (second.usePing) {
9131               char buf[MSG_SIZ];
9132               sprintf(buf, "ping %d\n", ++second.lastPing);
9133               SendToProgram(buf, &second);
9134             }
9135         }
9136     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9137         /* Kill off second chess program */
9138         if (second.isr != NULL)
9139           RemoveInputSource(second.isr);
9140         second.isr = NULL;
9141     
9142         if (second.pr != NoProc) {
9143             DoSleep( appData.delayBeforeQuit );
9144             SendToProgram("quit\n", &second);
9145             DoSleep( appData.delayAfterQuit );
9146             DestroyChildProcess(second.pr, second.useSigterm);
9147         }
9148         second.pr = NoProc;
9149     }
9150
9151     if (matchMode && gameMode == TwoMachinesPlay) {
9152         switch (result) {
9153         case WhiteWins:
9154           if (first.twoMachinesColor[0] == 'w') {
9155             first.matchWins++;
9156           } else {
9157             second.matchWins++;
9158           }
9159           break;
9160         case BlackWins:
9161           if (first.twoMachinesColor[0] == 'b') {
9162             first.matchWins++;
9163           } else {
9164             second.matchWins++;
9165           }
9166           break;
9167         default:
9168           break;
9169         }
9170         if (matchGame < appData.matchGames) {
9171             char *tmp;
9172             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9173                 tmp = first.twoMachinesColor;
9174                 first.twoMachinesColor = second.twoMachinesColor;
9175                 second.twoMachinesColor = tmp;
9176             }
9177             gameMode = nextGameMode;
9178             matchGame++;
9179             if(appData.matchPause>10000 || appData.matchPause<10)
9180                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9181             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9182             endingGame = 0; /* [HGM] crash */
9183             return;
9184         } else {
9185             char buf[MSG_SIZ];
9186             gameMode = nextGameMode;
9187             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9188                     first.tidy, second.tidy,
9189                     first.matchWins, second.matchWins,
9190                     appData.matchGames - (first.matchWins + second.matchWins));
9191             DisplayFatalError(buf, 0, 0);
9192         }
9193     }
9194     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9195         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9196       ExitAnalyzeMode();
9197     gameMode = nextGameMode;
9198     ModeHighlight();
9199     endingGame = 0;  /* [HGM] crash */
9200 }
9201
9202 /* Assumes program was just initialized (initString sent).
9203    Leaves program in force mode. */
9204 void
9205 FeedMovesToProgram(cps, upto) 
9206      ChessProgramState *cps;
9207      int upto;
9208 {
9209     int i;
9210     
9211     if (appData.debugMode)
9212       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9213               startedFromSetupPosition ? "position and " : "",
9214               backwardMostMove, upto, cps->which);
9215     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9216         // [HGM] variantswitch: make engine aware of new variant
9217         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9218                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9219         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9220         SendToProgram(buf, cps);
9221         currentlyInitializedVariant = gameInfo.variant;
9222     }
9223     SendToProgram("force\n", cps);
9224     if (startedFromSetupPosition) {
9225         SendBoard(cps, backwardMostMove);
9226     if (appData.debugMode) {
9227         fprintf(debugFP, "feedMoves\n");
9228     }
9229     }
9230     for (i = backwardMostMove; i < upto; i++) {
9231         SendMoveToProgram(i, cps);
9232     }
9233 }
9234
9235
9236 void
9237 ResurrectChessProgram()
9238 {
9239      /* The chess program may have exited.
9240         If so, restart it and feed it all the moves made so far. */
9241
9242     if (appData.noChessProgram || first.pr != NoProc) return;
9243     
9244     StartChessProgram(&first);
9245     InitChessProgram(&first, FALSE);
9246     FeedMovesToProgram(&first, currentMove);
9247
9248     if (!first.sendTime) {
9249         /* can't tell gnuchess what its clock should read,
9250            so we bow to its notion. */
9251         ResetClocks();
9252         timeRemaining[0][currentMove] = whiteTimeRemaining;
9253         timeRemaining[1][currentMove] = blackTimeRemaining;
9254     }
9255
9256     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9257                 appData.icsEngineAnalyze) && first.analysisSupport) {
9258       SendToProgram("analyze\n", &first);
9259       first.analyzing = TRUE;
9260     }
9261 }
9262
9263 /*
9264  * Button procedures
9265  */
9266 void
9267 Reset(redraw, init)
9268      int redraw, init;
9269 {
9270     int i;
9271
9272     if (appData.debugMode) {
9273         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9274                 redraw, init, gameMode);
9275     }
9276     CleanupTail(); // [HGM] vari: delete any stored variations
9277     pausing = pauseExamInvalid = FALSE;
9278     startedFromSetupPosition = blackPlaysFirst = FALSE;
9279     firstMove = TRUE;
9280     whiteFlag = blackFlag = FALSE;
9281     userOfferedDraw = FALSE;
9282     hintRequested = bookRequested = FALSE;
9283     first.maybeThinking = FALSE;
9284     second.maybeThinking = FALSE;
9285     first.bookSuspend = FALSE; // [HGM] book
9286     second.bookSuspend = FALSE;
9287     thinkOutput[0] = NULLCHAR;
9288     lastHint[0] = NULLCHAR;
9289     ClearGameInfo(&gameInfo);
9290     gameInfo.variant = StringToVariant(appData.variant);
9291     ics_user_moved = ics_clock_paused = FALSE;
9292     ics_getting_history = H_FALSE;
9293     ics_gamenum = -1;
9294     white_holding[0] = black_holding[0] = NULLCHAR;
9295     ClearProgramStats();
9296     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9297     
9298     ResetFrontEnd();
9299     ClearHighlights();
9300     flipView = appData.flipView;
9301     ClearPremoveHighlights();
9302     gotPremove = FALSE;
9303     alarmSounded = FALSE;
9304
9305     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9306     if(appData.serverMovesName != NULL) {
9307         /* [HGM] prepare to make moves file for broadcasting */
9308         clock_t t = clock();
9309         if(serverMoves != NULL) fclose(serverMoves);
9310         serverMoves = fopen(appData.serverMovesName, "r");
9311         if(serverMoves != NULL) {
9312             fclose(serverMoves);
9313             /* delay 15 sec before overwriting, so all clients can see end */
9314             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9315         }
9316         serverMoves = fopen(appData.serverMovesName, "w");
9317     }
9318
9319     ExitAnalyzeMode();
9320     gameMode = BeginningOfGame;
9321     ModeHighlight();
9322     if(appData.icsActive) gameInfo.variant = VariantNormal;
9323     currentMove = forwardMostMove = backwardMostMove = 0;
9324     InitPosition(redraw);
9325     for (i = 0; i < MAX_MOVES; i++) {
9326         if (commentList[i] != NULL) {
9327             free(commentList[i]);
9328             commentList[i] = NULL;
9329         }
9330     }
9331     ResetClocks();
9332     timeRemaining[0][0] = whiteTimeRemaining;
9333     timeRemaining[1][0] = blackTimeRemaining;
9334     if (first.pr == NULL) {
9335         StartChessProgram(&first);
9336     }
9337     if (init) {
9338             InitChessProgram(&first, startedFromSetupPosition);
9339     }
9340     DisplayTitle("");
9341     DisplayMessage("", "");
9342     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9343     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9344 }
9345
9346 void
9347 AutoPlayGameLoop()
9348 {
9349     for (;;) {
9350         if (!AutoPlayOneMove())
9351           return;
9352         if (matchMode || appData.timeDelay == 0)
9353           continue;
9354         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9355           return;
9356         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9357         break;
9358     }
9359 }
9360
9361
9362 int
9363 AutoPlayOneMove()
9364 {
9365     int fromX, fromY, toX, toY;
9366
9367     if (appData.debugMode) {
9368       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9369     }
9370
9371     if (gameMode != PlayFromGameFile)
9372       return FALSE;
9373
9374     if (currentMove >= forwardMostMove) {
9375       gameMode = EditGame;
9376       ModeHighlight();
9377
9378       /* [AS] Clear current move marker at the end of a game */
9379       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9380
9381       return FALSE;
9382     }
9383     
9384     toX = moveList[currentMove][2] - AAA;
9385     toY = moveList[currentMove][3] - ONE;
9386
9387     if (moveList[currentMove][1] == '@') {
9388         if (appData.highlightLastMove) {
9389             SetHighlights(-1, -1, toX, toY);
9390         }
9391     } else {
9392         fromX = moveList[currentMove][0] - AAA;
9393         fromY = moveList[currentMove][1] - ONE;
9394
9395         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9396
9397         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9398
9399         if (appData.highlightLastMove) {
9400             SetHighlights(fromX, fromY, toX, toY);
9401         }
9402     }
9403     DisplayMove(currentMove);
9404     SendMoveToProgram(currentMove++, &first);
9405     DisplayBothClocks();
9406     DrawPosition(FALSE, boards[currentMove]);
9407     // [HGM] PV info: always display, routine tests if empty
9408     DisplayComment(currentMove - 1, commentList[currentMove]);
9409     return TRUE;
9410 }
9411
9412
9413 int
9414 LoadGameOneMove(readAhead)
9415      ChessMove readAhead;
9416 {
9417     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9418     char promoChar = NULLCHAR;
9419     ChessMove moveType;
9420     char move[MSG_SIZ];
9421     char *p, *q;
9422     
9423     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9424         gameMode != AnalyzeMode && gameMode != Training) {
9425         gameFileFP = NULL;
9426         return FALSE;
9427     }
9428     
9429     yyboardindex = forwardMostMove;
9430     if (readAhead != (ChessMove)0) {
9431       moveType = readAhead;
9432     } else {
9433       if (gameFileFP == NULL)
9434           return FALSE;
9435       moveType = (ChessMove) yylex();
9436     }
9437     
9438     done = FALSE;
9439     switch (moveType) {
9440       case Comment:
9441         if (appData.debugMode) 
9442           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9443         p = yy_text;
9444
9445         /* append the comment but don't display it */
9446         AppendComment(currentMove, p, FALSE);
9447         return TRUE;
9448
9449       case WhiteCapturesEnPassant:
9450       case BlackCapturesEnPassant:
9451       case WhitePromotionChancellor:
9452       case BlackPromotionChancellor:
9453       case WhitePromotionArchbishop:
9454       case BlackPromotionArchbishop:
9455       case WhitePromotionCentaur:
9456       case BlackPromotionCentaur:
9457       case WhitePromotionQueen:
9458       case BlackPromotionQueen:
9459       case WhitePromotionRook:
9460       case BlackPromotionRook:
9461       case WhitePromotionBishop:
9462       case BlackPromotionBishop:
9463       case WhitePromotionKnight:
9464       case BlackPromotionKnight:
9465       case WhitePromotionKing:
9466       case BlackPromotionKing:
9467       case NormalMove:
9468       case WhiteKingSideCastle:
9469       case WhiteQueenSideCastle:
9470       case BlackKingSideCastle:
9471       case BlackQueenSideCastle:
9472       case WhiteKingSideCastleWild:
9473       case WhiteQueenSideCastleWild:
9474       case BlackKingSideCastleWild:
9475       case BlackQueenSideCastleWild:
9476       /* PUSH Fabien */
9477       case WhiteHSideCastleFR:
9478       case WhiteASideCastleFR:
9479       case BlackHSideCastleFR:
9480       case BlackASideCastleFR:
9481       /* POP Fabien */
9482         if (appData.debugMode)
9483           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9484         fromX = currentMoveString[0] - AAA;
9485         fromY = currentMoveString[1] - ONE;
9486         toX = currentMoveString[2] - AAA;
9487         toY = currentMoveString[3] - ONE;
9488         promoChar = currentMoveString[4];
9489         break;
9490
9491       case WhiteDrop:
9492       case BlackDrop:
9493         if (appData.debugMode)
9494           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9495         fromX = moveType == WhiteDrop ?
9496           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9497         (int) CharToPiece(ToLower(currentMoveString[0]));
9498         fromY = DROP_RANK;
9499         toX = currentMoveString[2] - AAA;
9500         toY = currentMoveString[3] - ONE;
9501         break;
9502
9503       case WhiteWins:
9504       case BlackWins:
9505       case GameIsDrawn:
9506       case GameUnfinished:
9507         if (appData.debugMode)
9508           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9509         p = strchr(yy_text, '{');
9510         if (p == NULL) p = strchr(yy_text, '(');
9511         if (p == NULL) {
9512             p = yy_text;
9513             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9514         } else {
9515             q = strchr(p, *p == '{' ? '}' : ')');
9516             if (q != NULL) *q = NULLCHAR;
9517             p++;
9518         }
9519         GameEnds(moveType, p, GE_FILE);
9520         done = TRUE;
9521         if (cmailMsgLoaded) {
9522             ClearHighlights();
9523             flipView = WhiteOnMove(currentMove);
9524             if (moveType == GameUnfinished) flipView = !flipView;
9525             if (appData.debugMode)
9526               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9527         }
9528         break;
9529
9530       case (ChessMove) 0:       /* end of file */
9531         if (appData.debugMode)
9532           fprintf(debugFP, "Parser hit end of file\n");
9533         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9534           case MT_NONE:
9535           case MT_CHECK:
9536             break;
9537           case MT_CHECKMATE:
9538           case MT_STAINMATE:
9539             if (WhiteOnMove(currentMove)) {
9540                 GameEnds(BlackWins, "Black mates", GE_FILE);
9541             } else {
9542                 GameEnds(WhiteWins, "White mates", GE_FILE);
9543             }
9544             break;
9545           case MT_STALEMATE:
9546             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9547             break;
9548         }
9549         done = TRUE;
9550         break;
9551
9552       case MoveNumberOne:
9553         if (lastLoadGameStart == GNUChessGame) {
9554             /* GNUChessGames have numbers, but they aren't move numbers */
9555             if (appData.debugMode)
9556               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9557                       yy_text, (int) moveType);
9558             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9559         }
9560         /* else fall thru */
9561
9562       case XBoardGame:
9563       case GNUChessGame:
9564       case PGNTag:
9565         /* Reached start of next game in file */
9566         if (appData.debugMode)
9567           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9568         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9569           case MT_NONE:
9570           case MT_CHECK:
9571             break;
9572           case MT_CHECKMATE:
9573           case MT_STAINMATE:
9574             if (WhiteOnMove(currentMove)) {
9575                 GameEnds(BlackWins, "Black mates", GE_FILE);
9576             } else {
9577                 GameEnds(WhiteWins, "White mates", GE_FILE);
9578             }
9579             break;
9580           case MT_STALEMATE:
9581             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9582             break;
9583         }
9584         done = TRUE;
9585         break;
9586
9587       case PositionDiagram:     /* should not happen; ignore */
9588       case ElapsedTime:         /* ignore */
9589       case NAG:                 /* ignore */
9590         if (appData.debugMode)
9591           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9592                   yy_text, (int) moveType);
9593         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9594
9595       case IllegalMove:
9596         if (appData.testLegality) {
9597             if (appData.debugMode)
9598               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9599             sprintf(move, _("Illegal move: %d.%s%s"),
9600                     (forwardMostMove / 2) + 1,
9601                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9602             DisplayError(move, 0);
9603             done = TRUE;
9604         } else {
9605             if (appData.debugMode)
9606               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9607                       yy_text, currentMoveString);
9608             fromX = currentMoveString[0] - AAA;
9609             fromY = currentMoveString[1] - ONE;
9610             toX = currentMoveString[2] - AAA;
9611             toY = currentMoveString[3] - ONE;
9612             promoChar = currentMoveString[4];
9613         }
9614         break;
9615
9616       case AmbiguousMove:
9617         if (appData.debugMode)
9618           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9619         sprintf(move, _("Ambiguous move: %d.%s%s"),
9620                 (forwardMostMove / 2) + 1,
9621                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9622         DisplayError(move, 0);
9623         done = TRUE;
9624         break;
9625
9626       default:
9627       case ImpossibleMove:
9628         if (appData.debugMode)
9629           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9630         sprintf(move, _("Illegal move: %d.%s%s"),
9631                 (forwardMostMove / 2) + 1,
9632                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9633         DisplayError(move, 0);
9634         done = TRUE;
9635         break;
9636     }
9637
9638     if (done) {
9639         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9640             DrawPosition(FALSE, boards[currentMove]);
9641             DisplayBothClocks();
9642             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9643               DisplayComment(currentMove - 1, commentList[currentMove]);
9644         }
9645         (void) StopLoadGameTimer();
9646         gameFileFP = NULL;
9647         cmailOldMove = forwardMostMove;
9648         return FALSE;
9649     } else {
9650         /* currentMoveString is set as a side-effect of yylex */
9651         strcat(currentMoveString, "\n");
9652         strcpy(moveList[forwardMostMove], currentMoveString);
9653         
9654         thinkOutput[0] = NULLCHAR;
9655         MakeMove(fromX, fromY, toX, toY, promoChar);
9656         currentMove = forwardMostMove;
9657         return TRUE;
9658     }
9659 }
9660
9661 /* Load the nth game from the given file */
9662 int
9663 LoadGameFromFile(filename, n, title, useList)
9664      char *filename;
9665      int n;
9666      char *title;
9667      /*Boolean*/ int useList;
9668 {
9669     FILE *f;
9670     char buf[MSG_SIZ];
9671
9672     if (strcmp(filename, "-") == 0) {
9673         f = stdin;
9674         title = "stdin";
9675     } else {
9676         f = fopen(filename, "rb");
9677         if (f == NULL) {
9678           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9679             DisplayError(buf, errno);
9680             return FALSE;
9681         }
9682     }
9683     if (fseek(f, 0, 0) == -1) {
9684         /* f is not seekable; probably a pipe */
9685         useList = FALSE;
9686     }
9687     if (useList && n == 0) {
9688         int error = GameListBuild(f);
9689         if (error) {
9690             DisplayError(_("Cannot build game list"), error);
9691         } else if (!ListEmpty(&gameList) &&
9692                    ((ListGame *) gameList.tailPred)->number > 1) {
9693             GameListPopUp(f, title);
9694             return TRUE;
9695         }
9696         GameListDestroy();
9697         n = 1;
9698     }
9699     if (n == 0) n = 1;
9700     return LoadGame(f, n, title, FALSE);
9701 }
9702
9703
9704 void
9705 MakeRegisteredMove()
9706 {
9707     int fromX, fromY, toX, toY;
9708     char promoChar;
9709     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9710         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9711           case CMAIL_MOVE:
9712           case CMAIL_DRAW:
9713             if (appData.debugMode)
9714               fprintf(debugFP, "Restoring %s for game %d\n",
9715                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9716     
9717             thinkOutput[0] = NULLCHAR;
9718             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9719             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9720             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9721             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9722             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9723             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9724             MakeMove(fromX, fromY, toX, toY, promoChar);
9725             ShowMove(fromX, fromY, toX, toY);
9726               
9727             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9728               case MT_NONE:
9729               case MT_CHECK:
9730                 break;
9731                 
9732               case MT_CHECKMATE:
9733               case MT_STAINMATE:
9734                 if (WhiteOnMove(currentMove)) {
9735                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9736                 } else {
9737                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9738                 }
9739                 break;
9740                 
9741               case MT_STALEMATE:
9742                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9743                 break;
9744             }
9745
9746             break;
9747             
9748           case CMAIL_RESIGN:
9749             if (WhiteOnMove(currentMove)) {
9750                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9751             } else {
9752                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9753             }
9754             break;
9755             
9756           case CMAIL_ACCEPT:
9757             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9758             break;
9759               
9760           default:
9761             break;
9762         }
9763     }
9764
9765     return;
9766 }
9767
9768 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9769 int
9770 CmailLoadGame(f, gameNumber, title, useList)
9771      FILE *f;
9772      int gameNumber;
9773      char *title;
9774      int useList;
9775 {
9776     int retVal;
9777
9778     if (gameNumber > nCmailGames) {
9779         DisplayError(_("No more games in this message"), 0);
9780         return FALSE;
9781     }
9782     if (f == lastLoadGameFP) {
9783         int offset = gameNumber - lastLoadGameNumber;
9784         if (offset == 0) {
9785             cmailMsg[0] = NULLCHAR;
9786             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9787                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9788                 nCmailMovesRegistered--;
9789             }
9790             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9791             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9792                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9793             }
9794         } else {
9795             if (! RegisterMove()) return FALSE;
9796         }
9797     }
9798
9799     retVal = LoadGame(f, gameNumber, title, useList);
9800
9801     /* Make move registered during previous look at this game, if any */
9802     MakeRegisteredMove();
9803
9804     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9805         commentList[currentMove]
9806           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9807         DisplayComment(currentMove - 1, commentList[currentMove]);
9808     }
9809
9810     return retVal;
9811 }
9812
9813 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9814 int
9815 ReloadGame(offset)
9816      int offset;
9817 {
9818     int gameNumber = lastLoadGameNumber + offset;
9819     if (lastLoadGameFP == NULL) {
9820         DisplayError(_("No game has been loaded yet"), 0);
9821         return FALSE;
9822     }
9823     if (gameNumber <= 0) {
9824         DisplayError(_("Can't back up any further"), 0);
9825         return FALSE;
9826     }
9827     if (cmailMsgLoaded) {
9828         return CmailLoadGame(lastLoadGameFP, gameNumber,
9829                              lastLoadGameTitle, lastLoadGameUseList);
9830     } else {
9831         return LoadGame(lastLoadGameFP, gameNumber,
9832                         lastLoadGameTitle, lastLoadGameUseList);
9833     }
9834 }
9835
9836
9837
9838 /* Load the nth game from open file f */
9839 int
9840 LoadGame(f, gameNumber, title, useList)
9841      FILE *f;
9842      int gameNumber;
9843      char *title;
9844      int useList;
9845 {
9846     ChessMove cm;
9847     char buf[MSG_SIZ];
9848     int gn = gameNumber;
9849     ListGame *lg = NULL;
9850     int numPGNTags = 0;
9851     int err;
9852     GameMode oldGameMode;
9853     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9854
9855     if (appData.debugMode) 
9856         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9857
9858     if (gameMode == Training )
9859         SetTrainingModeOff();
9860
9861     oldGameMode = gameMode;
9862     if (gameMode != BeginningOfGame) {
9863       Reset(FALSE, TRUE);
9864     }
9865
9866     gameFileFP = f;
9867     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9868         fclose(lastLoadGameFP);
9869     }
9870
9871     if (useList) {
9872         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9873         
9874         if (lg) {
9875             fseek(f, lg->offset, 0);
9876             GameListHighlight(gameNumber);
9877             gn = 1;
9878         }
9879         else {
9880             DisplayError(_("Game number out of range"), 0);
9881             return FALSE;
9882         }
9883     } else {
9884         GameListDestroy();
9885         if (fseek(f, 0, 0) == -1) {
9886             if (f == lastLoadGameFP ?
9887                 gameNumber == lastLoadGameNumber + 1 :
9888                 gameNumber == 1) {
9889                 gn = 1;
9890             } else {
9891                 DisplayError(_("Can't seek on game file"), 0);
9892                 return FALSE;
9893             }
9894         }
9895     }
9896     lastLoadGameFP = f;
9897     lastLoadGameNumber = gameNumber;
9898     strcpy(lastLoadGameTitle, title);
9899     lastLoadGameUseList = useList;
9900
9901     yynewfile(f);
9902
9903     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9904       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9905                 lg->gameInfo.black);
9906             DisplayTitle(buf);
9907     } else if (*title != NULLCHAR) {
9908         if (gameNumber > 1) {
9909             sprintf(buf, "%s %d", title, gameNumber);
9910             DisplayTitle(buf);
9911         } else {
9912             DisplayTitle(title);
9913         }
9914     }
9915
9916     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9917         gameMode = PlayFromGameFile;
9918         ModeHighlight();
9919     }
9920
9921     currentMove = forwardMostMove = backwardMostMove = 0;
9922     CopyBoard(boards[0], initialPosition);
9923     StopClocks();
9924
9925     /*
9926      * Skip the first gn-1 games in the file.
9927      * Also skip over anything that precedes an identifiable 
9928      * start of game marker, to avoid being confused by 
9929      * garbage at the start of the file.  Currently 
9930      * recognized start of game markers are the move number "1",
9931      * the pattern "gnuchess .* game", the pattern
9932      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9933      * A game that starts with one of the latter two patterns
9934      * will also have a move number 1, possibly
9935      * following a position diagram.
9936      * 5-4-02: Let's try being more lenient and allowing a game to
9937      * start with an unnumbered move.  Does that break anything?
9938      */
9939     cm = lastLoadGameStart = (ChessMove) 0;
9940     while (gn > 0) {
9941         yyboardindex = forwardMostMove;
9942         cm = (ChessMove) yylex();
9943         switch (cm) {
9944           case (ChessMove) 0:
9945             if (cmailMsgLoaded) {
9946                 nCmailGames = CMAIL_MAX_GAMES - gn;
9947             } else {
9948                 Reset(TRUE, TRUE);
9949                 DisplayError(_("Game not found in file"), 0);
9950             }
9951             return FALSE;
9952
9953           case GNUChessGame:
9954           case XBoardGame:
9955             gn--;
9956             lastLoadGameStart = cm;
9957             break;
9958             
9959           case MoveNumberOne:
9960             switch (lastLoadGameStart) {
9961               case GNUChessGame:
9962               case XBoardGame:
9963               case PGNTag:
9964                 break;
9965               case MoveNumberOne:
9966               case (ChessMove) 0:
9967                 gn--;           /* count this game */
9968                 lastLoadGameStart = cm;
9969                 break;
9970               default:
9971                 /* impossible */
9972                 break;
9973             }
9974             break;
9975
9976           case PGNTag:
9977             switch (lastLoadGameStart) {
9978               case GNUChessGame:
9979               case PGNTag:
9980               case MoveNumberOne:
9981               case (ChessMove) 0:
9982                 gn--;           /* count this game */
9983                 lastLoadGameStart = cm;
9984                 break;
9985               case XBoardGame:
9986                 lastLoadGameStart = cm; /* game counted already */
9987                 break;
9988               default:
9989                 /* impossible */
9990                 break;
9991             }
9992             if (gn > 0) {
9993                 do {
9994                     yyboardindex = forwardMostMove;
9995                     cm = (ChessMove) yylex();
9996                 } while (cm == PGNTag || cm == Comment);
9997             }
9998             break;
9999
10000           case WhiteWins:
10001           case BlackWins:
10002           case GameIsDrawn:
10003             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10004                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10005                     != CMAIL_OLD_RESULT) {
10006                     nCmailResults ++ ;
10007                     cmailResult[  CMAIL_MAX_GAMES
10008                                 - gn - 1] = CMAIL_OLD_RESULT;
10009                 }
10010             }
10011             break;
10012
10013           case NormalMove:
10014             /* Only a NormalMove can be at the start of a game
10015              * without a position diagram. */
10016             if (lastLoadGameStart == (ChessMove) 0) {
10017               gn--;
10018               lastLoadGameStart = MoveNumberOne;
10019             }
10020             break;
10021
10022           default:
10023             break;
10024         }
10025     }
10026     
10027     if (appData.debugMode)
10028       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10029
10030     if (cm == XBoardGame) {
10031         /* Skip any header junk before position diagram and/or move 1 */
10032         for (;;) {
10033             yyboardindex = forwardMostMove;
10034             cm = (ChessMove) yylex();
10035
10036             if (cm == (ChessMove) 0 ||
10037                 cm == GNUChessGame || cm == XBoardGame) {
10038                 /* Empty game; pretend end-of-file and handle later */
10039                 cm = (ChessMove) 0;
10040                 break;
10041             }
10042
10043             if (cm == MoveNumberOne || cm == PositionDiagram ||
10044                 cm == PGNTag || cm == Comment)
10045               break;
10046         }
10047     } else if (cm == GNUChessGame) {
10048         if (gameInfo.event != NULL) {
10049             free(gameInfo.event);
10050         }
10051         gameInfo.event = StrSave(yy_text);
10052     }   
10053
10054     startedFromSetupPosition = FALSE;
10055     while (cm == PGNTag) {
10056         if (appData.debugMode) 
10057           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10058         err = ParsePGNTag(yy_text, &gameInfo);
10059         if (!err) numPGNTags++;
10060
10061         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10062         if(gameInfo.variant != oldVariant) {
10063             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10064             InitPosition(TRUE);
10065             oldVariant = gameInfo.variant;
10066             if (appData.debugMode) 
10067               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10068         }
10069
10070
10071         if (gameInfo.fen != NULL) {
10072           Board initial_position;
10073           startedFromSetupPosition = TRUE;
10074           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10075             Reset(TRUE, TRUE);
10076             DisplayError(_("Bad FEN position in file"), 0);
10077             return FALSE;
10078           }
10079           CopyBoard(boards[0], initial_position);
10080           if (blackPlaysFirst) {
10081             currentMove = forwardMostMove = backwardMostMove = 1;
10082             CopyBoard(boards[1], initial_position);
10083             strcpy(moveList[0], "");
10084             strcpy(parseList[0], "");
10085             timeRemaining[0][1] = whiteTimeRemaining;
10086             timeRemaining[1][1] = blackTimeRemaining;
10087             if (commentList[0] != NULL) {
10088               commentList[1] = commentList[0];
10089               commentList[0] = NULL;
10090             }
10091           } else {
10092             currentMove = forwardMostMove = backwardMostMove = 0;
10093           }
10094           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10095           {   int i;
10096               initialRulePlies = FENrulePlies;
10097               for( i=0; i< nrCastlingRights; i++ )
10098                   initialRights[i] = initial_position[CASTLING][i];
10099           }
10100           yyboardindex = forwardMostMove;
10101           free(gameInfo.fen);
10102           gameInfo.fen = NULL;
10103         }
10104
10105         yyboardindex = forwardMostMove;
10106         cm = (ChessMove) yylex();
10107
10108         /* Handle comments interspersed among the tags */
10109         while (cm == Comment) {
10110             char *p;
10111             if (appData.debugMode) 
10112               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10113             p = yy_text;
10114             AppendComment(currentMove, p, FALSE);
10115             yyboardindex = forwardMostMove;
10116             cm = (ChessMove) yylex();
10117         }
10118     }
10119
10120     /* don't rely on existence of Event tag since if game was
10121      * pasted from clipboard the Event tag may not exist
10122      */
10123     if (numPGNTags > 0){
10124         char *tags;
10125         if (gameInfo.variant == VariantNormal) {
10126           gameInfo.variant = StringToVariant(gameInfo.event);
10127         }
10128         if (!matchMode) {
10129           if( appData.autoDisplayTags ) {
10130             tags = PGNTags(&gameInfo);
10131             TagsPopUp(tags, CmailMsg());
10132             free(tags);
10133           }
10134         }
10135     } else {
10136         /* Make something up, but don't display it now */
10137         SetGameInfo();
10138         TagsPopDown();
10139     }
10140
10141     if (cm == PositionDiagram) {
10142         int i, j;
10143         char *p;
10144         Board initial_position;
10145
10146         if (appData.debugMode)
10147           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10148
10149         if (!startedFromSetupPosition) {
10150             p = yy_text;
10151             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10152               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10153                 switch (*p) {
10154                   case '[':
10155                   case '-':
10156                   case ' ':
10157                   case '\t':
10158                   case '\n':
10159                   case '\r':
10160                     break;
10161                   default:
10162                     initial_position[i][j++] = CharToPiece(*p);
10163                     break;
10164                 }
10165             while (*p == ' ' || *p == '\t' ||
10166                    *p == '\n' || *p == '\r') p++;
10167         
10168             if (strncmp(p, "black", strlen("black"))==0)
10169               blackPlaysFirst = TRUE;
10170             else
10171               blackPlaysFirst = FALSE;
10172             startedFromSetupPosition = TRUE;
10173         
10174             CopyBoard(boards[0], initial_position);
10175             if (blackPlaysFirst) {
10176                 currentMove = forwardMostMove = backwardMostMove = 1;
10177                 CopyBoard(boards[1], initial_position);
10178                 strcpy(moveList[0], "");
10179                 strcpy(parseList[0], "");
10180                 timeRemaining[0][1] = whiteTimeRemaining;
10181                 timeRemaining[1][1] = blackTimeRemaining;
10182                 if (commentList[0] != NULL) {
10183                     commentList[1] = commentList[0];
10184                     commentList[0] = NULL;
10185                 }
10186             } else {
10187                 currentMove = forwardMostMove = backwardMostMove = 0;
10188             }
10189         }
10190         yyboardindex = forwardMostMove;
10191         cm = (ChessMove) yylex();
10192     }
10193
10194     if (first.pr == NoProc) {
10195         StartChessProgram(&first);
10196     }
10197     InitChessProgram(&first, FALSE);
10198     SendToProgram("force\n", &first);
10199     if (startedFromSetupPosition) {
10200         SendBoard(&first, forwardMostMove);
10201     if (appData.debugMode) {
10202         fprintf(debugFP, "Load Game\n");
10203     }
10204         DisplayBothClocks();
10205     }      
10206
10207     /* [HGM] server: flag to write setup moves in broadcast file as one */
10208     loadFlag = appData.suppressLoadMoves;
10209
10210     while (cm == Comment) {
10211         char *p;
10212         if (appData.debugMode) 
10213           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10214         p = yy_text;
10215         AppendComment(currentMove, p, FALSE);
10216         yyboardindex = forwardMostMove;
10217         cm = (ChessMove) yylex();
10218     }
10219
10220     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10221         cm == WhiteWins || cm == BlackWins ||
10222         cm == GameIsDrawn || cm == GameUnfinished) {
10223         DisplayMessage("", _("No moves in game"));
10224         if (cmailMsgLoaded) {
10225             if (appData.debugMode)
10226               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10227             ClearHighlights();
10228             flipView = FALSE;
10229         }
10230         DrawPosition(FALSE, boards[currentMove]);
10231         DisplayBothClocks();
10232         gameMode = EditGame;
10233         ModeHighlight();
10234         gameFileFP = NULL;
10235         cmailOldMove = 0;
10236         return TRUE;
10237     }
10238
10239     // [HGM] PV info: routine tests if comment empty
10240     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10241         DisplayComment(currentMove - 1, commentList[currentMove]);
10242     }
10243     if (!matchMode && appData.timeDelay != 0) 
10244       DrawPosition(FALSE, boards[currentMove]);
10245
10246     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10247       programStats.ok_to_send = 1;
10248     }
10249
10250     /* if the first token after the PGN tags is a move
10251      * and not move number 1, retrieve it from the parser 
10252      */
10253     if (cm != MoveNumberOne)
10254         LoadGameOneMove(cm);
10255
10256     /* load the remaining moves from the file */
10257     while (LoadGameOneMove((ChessMove)0)) {
10258       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10259       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10260     }
10261
10262     /* rewind to the start of the game */
10263     currentMove = backwardMostMove;
10264
10265     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10266
10267     if (oldGameMode == AnalyzeFile ||
10268         oldGameMode == AnalyzeMode) {
10269       AnalyzeFileEvent();
10270     }
10271
10272     if (matchMode || appData.timeDelay == 0) {
10273       ToEndEvent();
10274       gameMode = EditGame;
10275       ModeHighlight();
10276     } else if (appData.timeDelay > 0) {
10277       AutoPlayGameLoop();
10278     }
10279
10280     if (appData.debugMode) 
10281         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10282
10283     loadFlag = 0; /* [HGM] true game starts */
10284     return TRUE;
10285 }
10286
10287 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10288 int
10289 ReloadPosition(offset)
10290      int offset;
10291 {
10292     int positionNumber = lastLoadPositionNumber + offset;
10293     if (lastLoadPositionFP == NULL) {
10294         DisplayError(_("No position has been loaded yet"), 0);
10295         return FALSE;
10296     }
10297     if (positionNumber <= 0) {
10298         DisplayError(_("Can't back up any further"), 0);
10299         return FALSE;
10300     }
10301     return LoadPosition(lastLoadPositionFP, positionNumber,
10302                         lastLoadPositionTitle);
10303 }
10304
10305 /* Load the nth position from the given file */
10306 int
10307 LoadPositionFromFile(filename, n, title)
10308      char *filename;
10309      int n;
10310      char *title;
10311 {
10312     FILE *f;
10313     char buf[MSG_SIZ];
10314
10315     if (strcmp(filename, "-") == 0) {
10316         return LoadPosition(stdin, n, "stdin");
10317     } else {
10318         f = fopen(filename, "rb");
10319         if (f == NULL) {
10320             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10321             DisplayError(buf, errno);
10322             return FALSE;
10323         } else {
10324             return LoadPosition(f, n, title);
10325         }
10326     }
10327 }
10328
10329 /* Load the nth position from the given open file, and close it */
10330 int
10331 LoadPosition(f, positionNumber, title)
10332      FILE *f;
10333      int positionNumber;
10334      char *title;
10335 {
10336     char *p, line[MSG_SIZ];
10337     Board initial_position;
10338     int i, j, fenMode, pn;
10339     
10340     if (gameMode == Training )
10341         SetTrainingModeOff();
10342
10343     if (gameMode != BeginningOfGame) {
10344         Reset(FALSE, TRUE);
10345     }
10346     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10347         fclose(lastLoadPositionFP);
10348     }
10349     if (positionNumber == 0) positionNumber = 1;
10350     lastLoadPositionFP = f;
10351     lastLoadPositionNumber = positionNumber;
10352     strcpy(lastLoadPositionTitle, title);
10353     if (first.pr == NoProc) {
10354       StartChessProgram(&first);
10355       InitChessProgram(&first, FALSE);
10356     }    
10357     pn = positionNumber;
10358     if (positionNumber < 0) {
10359         /* Negative position number means to seek to that byte offset */
10360         if (fseek(f, -positionNumber, 0) == -1) {
10361             DisplayError(_("Can't seek on position file"), 0);
10362             return FALSE;
10363         };
10364         pn = 1;
10365     } else {
10366         if (fseek(f, 0, 0) == -1) {
10367             if (f == lastLoadPositionFP ?
10368                 positionNumber == lastLoadPositionNumber + 1 :
10369                 positionNumber == 1) {
10370                 pn = 1;
10371             } else {
10372                 DisplayError(_("Can't seek on position file"), 0);
10373                 return FALSE;
10374             }
10375         }
10376     }
10377     /* See if this file is FEN or old-style xboard */
10378     if (fgets(line, MSG_SIZ, f) == NULL) {
10379         DisplayError(_("Position not found in file"), 0);
10380         return FALSE;
10381     }
10382     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10383     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10384
10385     if (pn >= 2) {
10386         if (fenMode || line[0] == '#') pn--;
10387         while (pn > 0) {
10388             /* skip positions before number pn */
10389             if (fgets(line, MSG_SIZ, f) == NULL) {
10390                 Reset(TRUE, TRUE);
10391                 DisplayError(_("Position not found in file"), 0);
10392                 return FALSE;
10393             }
10394             if (fenMode || line[0] == '#') pn--;
10395         }
10396     }
10397
10398     if (fenMode) {
10399         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10400             DisplayError(_("Bad FEN position in file"), 0);
10401             return FALSE;
10402         }
10403     } else {
10404         (void) fgets(line, MSG_SIZ, f);
10405         (void) fgets(line, MSG_SIZ, f);
10406     
10407         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10408             (void) fgets(line, MSG_SIZ, f);
10409             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10410                 if (*p == ' ')
10411                   continue;
10412                 initial_position[i][j++] = CharToPiece(*p);
10413             }
10414         }
10415     
10416         blackPlaysFirst = FALSE;
10417         if (!feof(f)) {
10418             (void) fgets(line, MSG_SIZ, f);
10419             if (strncmp(line, "black", strlen("black"))==0)
10420               blackPlaysFirst = TRUE;
10421         }
10422     }
10423     startedFromSetupPosition = TRUE;
10424     
10425     SendToProgram("force\n", &first);
10426     CopyBoard(boards[0], initial_position);
10427     if (blackPlaysFirst) {
10428         currentMove = forwardMostMove = backwardMostMove = 1;
10429         strcpy(moveList[0], "");
10430         strcpy(parseList[0], "");
10431         CopyBoard(boards[1], initial_position);
10432         DisplayMessage("", _("Black to play"));
10433     } else {
10434         currentMove = forwardMostMove = backwardMostMove = 0;
10435         DisplayMessage("", _("White to play"));
10436     }
10437     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10438     SendBoard(&first, forwardMostMove);
10439     if (appData.debugMode) {
10440 int i, j;
10441   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10442   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10443         fprintf(debugFP, "Load Position\n");
10444     }
10445
10446     if (positionNumber > 1) {
10447         sprintf(line, "%s %d", title, positionNumber);
10448         DisplayTitle(line);
10449     } else {
10450         DisplayTitle(title);
10451     }
10452     gameMode = EditGame;
10453     ModeHighlight();
10454     ResetClocks();
10455     timeRemaining[0][1] = whiteTimeRemaining;
10456     timeRemaining[1][1] = blackTimeRemaining;
10457     DrawPosition(FALSE, boards[currentMove]);
10458    
10459     return TRUE;
10460 }
10461
10462
10463 void
10464 CopyPlayerNameIntoFileName(dest, src)
10465      char **dest, *src;
10466 {
10467     while (*src != NULLCHAR && *src != ',') {
10468         if (*src == ' ') {
10469             *(*dest)++ = '_';
10470             src++;
10471         } else {
10472             *(*dest)++ = *src++;
10473         }
10474     }
10475 }
10476
10477 char *DefaultFileName(ext)
10478      char *ext;
10479 {
10480     static char def[MSG_SIZ];
10481     char *p;
10482
10483     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10484         p = def;
10485         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10486         *p++ = '-';
10487         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10488         *p++ = '.';
10489         strcpy(p, ext);
10490     } else {
10491         def[0] = NULLCHAR;
10492     }
10493     return def;
10494 }
10495
10496 /* Save the current game to the given file */
10497 int
10498 SaveGameToFile(filename, append)
10499      char *filename;
10500      int append;
10501 {
10502     FILE *f;
10503     char buf[MSG_SIZ];
10504
10505     if (strcmp(filename, "-") == 0) {
10506         return SaveGame(stdout, 0, NULL);
10507     } else {
10508         f = fopen(filename, append ? "a" : "w");
10509         if (f == NULL) {
10510             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10511             DisplayError(buf, errno);
10512             return FALSE;
10513         } else {
10514             return SaveGame(f, 0, NULL);
10515         }
10516     }
10517 }
10518
10519 char *
10520 SavePart(str)
10521      char *str;
10522 {
10523     static char buf[MSG_SIZ];
10524     char *p;
10525     
10526     p = strchr(str, ' ');
10527     if (p == NULL) return str;
10528     strncpy(buf, str, p - str);
10529     buf[p - str] = NULLCHAR;
10530     return buf;
10531 }
10532
10533 #define PGN_MAX_LINE 75
10534
10535 #define PGN_SIDE_WHITE  0
10536 #define PGN_SIDE_BLACK  1
10537
10538 /* [AS] */
10539 static int FindFirstMoveOutOfBook( int side )
10540 {
10541     int result = -1;
10542
10543     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10544         int index = backwardMostMove;
10545         int has_book_hit = 0;
10546
10547         if( (index % 2) != side ) {
10548             index++;
10549         }
10550
10551         while( index < forwardMostMove ) {
10552             /* Check to see if engine is in book */
10553             int depth = pvInfoList[index].depth;
10554             int score = pvInfoList[index].score;
10555             int in_book = 0;
10556
10557             if( depth <= 2 ) {
10558                 in_book = 1;
10559             }
10560             else if( score == 0 && depth == 63 ) {
10561                 in_book = 1; /* Zappa */
10562             }
10563             else if( score == 2 && depth == 99 ) {
10564                 in_book = 1; /* Abrok */
10565             }
10566
10567             has_book_hit += in_book;
10568
10569             if( ! in_book ) {
10570                 result = index;
10571
10572                 break;
10573             }
10574
10575             index += 2;
10576         }
10577     }
10578
10579     return result;
10580 }
10581
10582 /* [AS] */
10583 void GetOutOfBookInfo( char * buf )
10584 {
10585     int oob[2];
10586     int i;
10587     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10588
10589     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10590     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10591
10592     *buf = '\0';
10593
10594     if( oob[0] >= 0 || oob[1] >= 0 ) {
10595         for( i=0; i<2; i++ ) {
10596             int idx = oob[i];
10597
10598             if( idx >= 0 ) {
10599                 if( i > 0 && oob[0] >= 0 ) {
10600                     strcat( buf, "   " );
10601                 }
10602
10603                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10604                 sprintf( buf+strlen(buf), "%s%.2f", 
10605                     pvInfoList[idx].score >= 0 ? "+" : "",
10606                     pvInfoList[idx].score / 100.0 );
10607             }
10608         }
10609     }
10610 }
10611
10612 /* Save game in PGN style and close the file */
10613 int
10614 SaveGamePGN(f)
10615      FILE *f;
10616 {
10617     int i, offset, linelen, newblock;
10618     time_t tm;
10619 //    char *movetext;
10620     char numtext[32];
10621     int movelen, numlen, blank;
10622     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10623
10624     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10625     
10626     tm = time((time_t *) NULL);
10627     
10628     PrintPGNTags(f, &gameInfo);
10629     
10630     if (backwardMostMove > 0 || startedFromSetupPosition) {
10631         char *fen = PositionToFEN(backwardMostMove, NULL);
10632         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10633         fprintf(f, "\n{--------------\n");
10634         PrintPosition(f, backwardMostMove);
10635         fprintf(f, "--------------}\n");
10636         free(fen);
10637     }
10638     else {
10639         /* [AS] Out of book annotation */
10640         if( appData.saveOutOfBookInfo ) {
10641             char buf[64];
10642
10643             GetOutOfBookInfo( buf );
10644
10645             if( buf[0] != '\0' ) {
10646                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10647             }
10648         }
10649
10650         fprintf(f, "\n");
10651     }
10652
10653     i = backwardMostMove;
10654     linelen = 0;
10655     newblock = TRUE;
10656
10657     while (i < forwardMostMove) {
10658         /* Print comments preceding this move */
10659         if (commentList[i] != NULL) {
10660             if (linelen > 0) fprintf(f, "\n");
10661             fprintf(f, "%s", commentList[i]);
10662             linelen = 0;
10663             newblock = TRUE;
10664         }
10665
10666         /* Format move number */
10667         if ((i % 2) == 0) {
10668             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10669         } else {
10670             if (newblock) {
10671                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10672             } else {
10673                 numtext[0] = NULLCHAR;
10674             }
10675         }
10676         numlen = strlen(numtext);
10677         newblock = FALSE;
10678
10679         /* Print move number */
10680         blank = linelen > 0 && numlen > 0;
10681         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10682             fprintf(f, "\n");
10683             linelen = 0;
10684             blank = 0;
10685         }
10686         if (blank) {
10687             fprintf(f, " ");
10688             linelen++;
10689         }
10690         fprintf(f, "%s", numtext);
10691         linelen += numlen;
10692
10693         /* Get move */
10694         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10695         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10696
10697         /* Print move */
10698         blank = linelen > 0 && movelen > 0;
10699         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10700             fprintf(f, "\n");
10701             linelen = 0;
10702             blank = 0;
10703         }
10704         if (blank) {
10705             fprintf(f, " ");
10706             linelen++;
10707         }
10708         fprintf(f, "%s", move_buffer);
10709         linelen += movelen;
10710
10711         /* [AS] Add PV info if present */
10712         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10713             /* [HGM] add time */
10714             char buf[MSG_SIZ]; int seconds;
10715
10716             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10717
10718             if( seconds <= 0) buf[0] = 0; else
10719             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10720                 seconds = (seconds + 4)/10; // round to full seconds
10721                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10722                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10723             }
10724
10725             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10726                 pvInfoList[i].score >= 0 ? "+" : "",
10727                 pvInfoList[i].score / 100.0,
10728                 pvInfoList[i].depth,
10729                 buf );
10730
10731             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10732
10733             /* Print score/depth */
10734             blank = linelen > 0 && movelen > 0;
10735             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10736                 fprintf(f, "\n");
10737                 linelen = 0;
10738                 blank = 0;
10739             }
10740             if (blank) {
10741                 fprintf(f, " ");
10742                 linelen++;
10743             }
10744             fprintf(f, "%s", move_buffer);
10745             linelen += movelen;
10746         }
10747
10748         i++;
10749     }
10750     
10751     /* Start a new line */
10752     if (linelen > 0) fprintf(f, "\n");
10753
10754     /* Print comments after last move */
10755     if (commentList[i] != NULL) {
10756         fprintf(f, "%s\n", commentList[i]);
10757     }
10758
10759     /* Print result */
10760     if (gameInfo.resultDetails != NULL &&
10761         gameInfo.resultDetails[0] != NULLCHAR) {
10762         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10763                 PGNResult(gameInfo.result));
10764     } else {
10765         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10766     }
10767
10768     fclose(f);
10769     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10770     return TRUE;
10771 }
10772
10773 /* Save game in old style and close the file */
10774 int
10775 SaveGameOldStyle(f)
10776      FILE *f;
10777 {
10778     int i, offset;
10779     time_t tm;
10780     
10781     tm = time((time_t *) NULL);
10782     
10783     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10784     PrintOpponents(f);
10785     
10786     if (backwardMostMove > 0 || startedFromSetupPosition) {
10787         fprintf(f, "\n[--------------\n");
10788         PrintPosition(f, backwardMostMove);
10789         fprintf(f, "--------------]\n");
10790     } else {
10791         fprintf(f, "\n");
10792     }
10793
10794     i = backwardMostMove;
10795     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10796
10797     while (i < forwardMostMove) {
10798         if (commentList[i] != NULL) {
10799             fprintf(f, "[%s]\n", commentList[i]);
10800         }
10801
10802         if ((i % 2) == 1) {
10803             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10804             i++;
10805         } else {
10806             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10807             i++;
10808             if (commentList[i] != NULL) {
10809                 fprintf(f, "\n");
10810                 continue;
10811             }
10812             if (i >= forwardMostMove) {
10813                 fprintf(f, "\n");
10814                 break;
10815             }
10816             fprintf(f, "%s\n", parseList[i]);
10817             i++;
10818         }
10819     }
10820     
10821     if (commentList[i] != NULL) {
10822         fprintf(f, "[%s]\n", commentList[i]);
10823     }
10824
10825     /* This isn't really the old style, but it's close enough */
10826     if (gameInfo.resultDetails != NULL &&
10827         gameInfo.resultDetails[0] != NULLCHAR) {
10828         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10829                 gameInfo.resultDetails);
10830     } else {
10831         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10832     }
10833
10834     fclose(f);
10835     return TRUE;
10836 }
10837
10838 /* Save the current game to open file f and close the file */
10839 int
10840 SaveGame(f, dummy, dummy2)
10841      FILE *f;
10842      int dummy;
10843      char *dummy2;
10844 {
10845     if (gameMode == EditPosition) EditPositionDone(TRUE);
10846     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10847     if (appData.oldSaveStyle)
10848       return SaveGameOldStyle(f);
10849     else
10850       return SaveGamePGN(f);
10851 }
10852
10853 /* Save the current position to the given file */
10854 int
10855 SavePositionToFile(filename)
10856      char *filename;
10857 {
10858     FILE *f;
10859     char buf[MSG_SIZ];
10860
10861     if (strcmp(filename, "-") == 0) {
10862         return SavePosition(stdout, 0, NULL);
10863     } else {
10864         f = fopen(filename, "a");
10865         if (f == NULL) {
10866             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10867             DisplayError(buf, errno);
10868             return FALSE;
10869         } else {
10870             SavePosition(f, 0, NULL);
10871             return TRUE;
10872         }
10873     }
10874 }
10875
10876 /* Save the current position to the given open file and close the file */
10877 int
10878 SavePosition(f, dummy, dummy2)
10879      FILE *f;
10880      int dummy;
10881      char *dummy2;
10882 {
10883     time_t tm;
10884     char *fen;
10885     
10886     if (gameMode == EditPosition) EditPositionDone(TRUE);
10887     if (appData.oldSaveStyle) {
10888         tm = time((time_t *) NULL);
10889     
10890         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10891         PrintOpponents(f);
10892         fprintf(f, "[--------------\n");
10893         PrintPosition(f, currentMove);
10894         fprintf(f, "--------------]\n");
10895     } else {
10896         fen = PositionToFEN(currentMove, NULL);
10897         fprintf(f, "%s\n", fen);
10898         free(fen);
10899     }
10900     fclose(f);
10901     return TRUE;
10902 }
10903
10904 void
10905 ReloadCmailMsgEvent(unregister)
10906      int unregister;
10907 {
10908 #if !WIN32
10909     static char *inFilename = NULL;
10910     static char *outFilename;
10911     int i;
10912     struct stat inbuf, outbuf;
10913     int status;
10914     
10915     /* Any registered moves are unregistered if unregister is set, */
10916     /* i.e. invoked by the signal handler */
10917     if (unregister) {
10918         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10919             cmailMoveRegistered[i] = FALSE;
10920             if (cmailCommentList[i] != NULL) {
10921                 free(cmailCommentList[i]);
10922                 cmailCommentList[i] = NULL;
10923             }
10924         }
10925         nCmailMovesRegistered = 0;
10926     }
10927
10928     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10929         cmailResult[i] = CMAIL_NOT_RESULT;
10930     }
10931     nCmailResults = 0;
10932
10933     if (inFilename == NULL) {
10934         /* Because the filenames are static they only get malloced once  */
10935         /* and they never get freed                                      */
10936         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10937         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10938
10939         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10940         sprintf(outFilename, "%s.out", appData.cmailGameName);
10941     }
10942     
10943     status = stat(outFilename, &outbuf);
10944     if (status < 0) {
10945         cmailMailedMove = FALSE;
10946     } else {
10947         status = stat(inFilename, &inbuf);
10948         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10949     }
10950     
10951     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10952        counts the games, notes how each one terminated, etc.
10953        
10954        It would be nice to remove this kludge and instead gather all
10955        the information while building the game list.  (And to keep it
10956        in the game list nodes instead of having a bunch of fixed-size
10957        parallel arrays.)  Note this will require getting each game's
10958        termination from the PGN tags, as the game list builder does
10959        not process the game moves.  --mann
10960        */
10961     cmailMsgLoaded = TRUE;
10962     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10963     
10964     /* Load first game in the file or popup game menu */
10965     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10966
10967 #endif /* !WIN32 */
10968     return;
10969 }
10970
10971 int
10972 RegisterMove()
10973 {
10974     FILE *f;
10975     char string[MSG_SIZ];
10976
10977     if (   cmailMailedMove
10978         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10979         return TRUE;            /* Allow free viewing  */
10980     }
10981
10982     /* Unregister move to ensure that we don't leave RegisterMove        */
10983     /* with the move registered when the conditions for registering no   */
10984     /* longer hold                                                       */
10985     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10986         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10987         nCmailMovesRegistered --;
10988
10989         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10990           {
10991               free(cmailCommentList[lastLoadGameNumber - 1]);
10992               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10993           }
10994     }
10995
10996     if (cmailOldMove == -1) {
10997         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10998         return FALSE;
10999     }
11000
11001     if (currentMove > cmailOldMove + 1) {
11002         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11003         return FALSE;
11004     }
11005
11006     if (currentMove < cmailOldMove) {
11007         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11008         return FALSE;
11009     }
11010
11011     if (forwardMostMove > currentMove) {
11012         /* Silently truncate extra moves */
11013         TruncateGame();
11014     }
11015
11016     if (   (currentMove == cmailOldMove + 1)
11017         || (   (currentMove == cmailOldMove)
11018             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11019                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11020         if (gameInfo.result != GameUnfinished) {
11021             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11022         }
11023
11024         if (commentList[currentMove] != NULL) {
11025             cmailCommentList[lastLoadGameNumber - 1]
11026               = StrSave(commentList[currentMove]);
11027         }
11028         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11029
11030         if (appData.debugMode)
11031           fprintf(debugFP, "Saving %s for game %d\n",
11032                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11033
11034         sprintf(string,
11035                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11036         
11037         f = fopen(string, "w");
11038         if (appData.oldSaveStyle) {
11039             SaveGameOldStyle(f); /* also closes the file */
11040             
11041             sprintf(string, "%s.pos.out", appData.cmailGameName);
11042             f = fopen(string, "w");
11043             SavePosition(f, 0, NULL); /* also closes the file */
11044         } else {
11045             fprintf(f, "{--------------\n");
11046             PrintPosition(f, currentMove);
11047             fprintf(f, "--------------}\n\n");
11048             
11049             SaveGame(f, 0, NULL); /* also closes the file*/
11050         }
11051         
11052         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11053         nCmailMovesRegistered ++;
11054     } else if (nCmailGames == 1) {
11055         DisplayError(_("You have not made a move yet"), 0);
11056         return FALSE;
11057     }
11058
11059     return TRUE;
11060 }
11061
11062 void
11063 MailMoveEvent()
11064 {
11065 #if !WIN32
11066     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11067     FILE *commandOutput;
11068     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11069     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11070     int nBuffers;
11071     int i;
11072     int archived;
11073     char *arcDir;
11074
11075     if (! cmailMsgLoaded) {
11076         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11077         return;
11078     }
11079
11080     if (nCmailGames == nCmailResults) {
11081         DisplayError(_("No unfinished games"), 0);
11082         return;
11083     }
11084
11085 #if CMAIL_PROHIBIT_REMAIL
11086     if (cmailMailedMove) {
11087         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);
11088         DisplayError(msg, 0);
11089         return;
11090     }
11091 #endif
11092
11093     if (! (cmailMailedMove || RegisterMove())) return;
11094     
11095     if (   cmailMailedMove
11096         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11097         sprintf(string, partCommandString,
11098                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11099         commandOutput = popen(string, "r");
11100
11101         if (commandOutput == NULL) {
11102             DisplayError(_("Failed to invoke cmail"), 0);
11103         } else {
11104             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11105                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11106             }
11107             if (nBuffers > 1) {
11108                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11109                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11110                 nBytes = MSG_SIZ - 1;
11111             } else {
11112                 (void) memcpy(msg, buffer, nBytes);
11113             }
11114             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11115
11116             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11117                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11118
11119                 archived = TRUE;
11120                 for (i = 0; i < nCmailGames; i ++) {
11121                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11122                         archived = FALSE;
11123                     }
11124                 }
11125                 if (   archived
11126                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11127                         != NULL)) {
11128                     sprintf(buffer, "%s/%s.%s.archive",
11129                             arcDir,
11130                             appData.cmailGameName,
11131                             gameInfo.date);
11132                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11133                     cmailMsgLoaded = FALSE;
11134                 }
11135             }
11136
11137             DisplayInformation(msg);
11138             pclose(commandOutput);
11139         }
11140     } else {
11141         if ((*cmailMsg) != '\0') {
11142             DisplayInformation(cmailMsg);
11143         }
11144     }
11145
11146     return;
11147 #endif /* !WIN32 */
11148 }
11149
11150 char *
11151 CmailMsg()
11152 {
11153 #if WIN32
11154     return NULL;
11155 #else
11156     int  prependComma = 0;
11157     char number[5];
11158     char string[MSG_SIZ];       /* Space for game-list */
11159     int  i;
11160     
11161     if (!cmailMsgLoaded) return "";
11162
11163     if (cmailMailedMove) {
11164         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11165     } else {
11166         /* Create a list of games left */
11167         sprintf(string, "[");
11168         for (i = 0; i < nCmailGames; i ++) {
11169             if (! (   cmailMoveRegistered[i]
11170                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11171                 if (prependComma) {
11172                     sprintf(number, ",%d", i + 1);
11173                 } else {
11174                     sprintf(number, "%d", i + 1);
11175                     prependComma = 1;
11176                 }
11177                 
11178                 strcat(string, number);
11179             }
11180         }
11181         strcat(string, "]");
11182
11183         if (nCmailMovesRegistered + nCmailResults == 0) {
11184             switch (nCmailGames) {
11185               case 1:
11186                 sprintf(cmailMsg,
11187                         _("Still need to make move for game\n"));
11188                 break;
11189                 
11190               case 2:
11191                 sprintf(cmailMsg,
11192                         _("Still need to make moves for both games\n"));
11193                 break;
11194                 
11195               default:
11196                 sprintf(cmailMsg,
11197                         _("Still need to make moves for all %d games\n"),
11198                         nCmailGames);
11199                 break;
11200             }
11201         } else {
11202             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11203               case 1:
11204                 sprintf(cmailMsg,
11205                         _("Still need to make a move for game %s\n"),
11206                         string);
11207                 break;
11208                 
11209               case 0:
11210                 if (nCmailResults == nCmailGames) {
11211                     sprintf(cmailMsg, _("No unfinished games\n"));
11212                 } else {
11213                     sprintf(cmailMsg, _("Ready to send mail\n"));
11214                 }
11215                 break;
11216                 
11217               default:
11218                 sprintf(cmailMsg,
11219                         _("Still need to make moves for games %s\n"),
11220                         string);
11221             }
11222         }
11223     }
11224     return cmailMsg;
11225 #endif /* WIN32 */
11226 }
11227
11228 void
11229 ResetGameEvent()
11230 {
11231     if (gameMode == Training)
11232       SetTrainingModeOff();
11233
11234     Reset(TRUE, TRUE);
11235     cmailMsgLoaded = FALSE;
11236     if (appData.icsActive) {
11237       SendToICS(ics_prefix);
11238       SendToICS("refresh\n");
11239     }
11240 }
11241
11242 void
11243 ExitEvent(status)
11244      int status;
11245 {
11246     exiting++;
11247     if (exiting > 2) {
11248       /* Give up on clean exit */
11249       exit(status);
11250     }
11251     if (exiting > 1) {
11252       /* Keep trying for clean exit */
11253       return;
11254     }
11255
11256     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11257
11258     if (telnetISR != NULL) {
11259       RemoveInputSource(telnetISR);
11260     }
11261     if (icsPR != NoProc) {
11262       DestroyChildProcess(icsPR, TRUE);
11263     }
11264
11265     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11266     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11267
11268     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11269     /* make sure this other one finishes before killing it!                  */
11270     if(endingGame) { int count = 0;
11271         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11272         while(endingGame && count++ < 10) DoSleep(1);
11273         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11274     }
11275
11276     /* Kill off chess programs */
11277     if (first.pr != NoProc) {
11278         ExitAnalyzeMode();
11279         
11280         DoSleep( appData.delayBeforeQuit );
11281         SendToProgram("quit\n", &first);
11282         DoSleep( appData.delayAfterQuit );
11283         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11284     }
11285     if (second.pr != NoProc) {
11286         DoSleep( appData.delayBeforeQuit );
11287         SendToProgram("quit\n", &second);
11288         DoSleep( appData.delayAfterQuit );
11289         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11290     }
11291     if (first.isr != NULL) {
11292         RemoveInputSource(first.isr);
11293     }
11294     if (second.isr != NULL) {
11295         RemoveInputSource(second.isr);
11296     }
11297
11298     ShutDownFrontEnd();
11299     exit(status);
11300 }
11301
11302 void
11303 PauseEvent()
11304 {
11305     if (appData.debugMode)
11306         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11307     if (pausing) {
11308         pausing = FALSE;
11309         ModeHighlight();
11310         if (gameMode == MachinePlaysWhite ||
11311             gameMode == MachinePlaysBlack) {
11312             StartClocks();
11313         } else {
11314             DisplayBothClocks();
11315         }
11316         if (gameMode == PlayFromGameFile) {
11317             if (appData.timeDelay >= 0) 
11318                 AutoPlayGameLoop();
11319         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11320             Reset(FALSE, TRUE);
11321             SendToICS(ics_prefix);
11322             SendToICS("refresh\n");
11323         } else if (currentMove < forwardMostMove) {
11324             ForwardInner(forwardMostMove);
11325         }
11326         pauseExamInvalid = FALSE;
11327     } else {
11328         switch (gameMode) {
11329           default:
11330             return;
11331           case IcsExamining:
11332             pauseExamForwardMostMove = forwardMostMove;
11333             pauseExamInvalid = FALSE;
11334             /* fall through */
11335           case IcsObserving:
11336           case IcsPlayingWhite:
11337           case IcsPlayingBlack:
11338             pausing = TRUE;
11339             ModeHighlight();
11340             return;
11341           case PlayFromGameFile:
11342             (void) StopLoadGameTimer();
11343             pausing = TRUE;
11344             ModeHighlight();
11345             break;
11346           case BeginningOfGame:
11347             if (appData.icsActive) return;
11348             /* else fall through */
11349           case MachinePlaysWhite:
11350           case MachinePlaysBlack:
11351           case TwoMachinesPlay:
11352             if (forwardMostMove == 0)
11353               return;           /* don't pause if no one has moved */
11354             if ((gameMode == MachinePlaysWhite &&
11355                  !WhiteOnMove(forwardMostMove)) ||
11356                 (gameMode == MachinePlaysBlack &&
11357                  WhiteOnMove(forwardMostMove))) {
11358                 StopClocks();
11359             }
11360             pausing = TRUE;
11361             ModeHighlight();
11362             break;
11363         }
11364     }
11365 }
11366
11367 void
11368 EditCommentEvent()
11369 {
11370     char title[MSG_SIZ];
11371
11372     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11373         strcpy(title, _("Edit comment"));
11374     } else {
11375         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11376                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11377                 parseList[currentMove - 1]);
11378     }
11379
11380     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11381 }
11382
11383
11384 void
11385 EditTagsEvent()
11386 {
11387     char *tags = PGNTags(&gameInfo);
11388     EditTagsPopUp(tags);
11389     free(tags);
11390 }
11391
11392 void
11393 AnalyzeModeEvent()
11394 {
11395     if (appData.noChessProgram || gameMode == AnalyzeMode)
11396       return;
11397
11398     if (gameMode != AnalyzeFile) {
11399         if (!appData.icsEngineAnalyze) {
11400                EditGameEvent();
11401                if (gameMode != EditGame) return;
11402         }
11403         ResurrectChessProgram();
11404         SendToProgram("analyze\n", &first);
11405         first.analyzing = TRUE;
11406         /*first.maybeThinking = TRUE;*/
11407         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11408         EngineOutputPopUp();
11409     }
11410     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11411     pausing = FALSE;
11412     ModeHighlight();
11413     SetGameInfo();
11414
11415     StartAnalysisClock();
11416     GetTimeMark(&lastNodeCountTime);
11417     lastNodeCount = 0;
11418 }
11419
11420 void
11421 AnalyzeFileEvent()
11422 {
11423     if (appData.noChessProgram || gameMode == AnalyzeFile)
11424       return;
11425
11426     if (gameMode != AnalyzeMode) {
11427         EditGameEvent();
11428         if (gameMode != EditGame) return;
11429         ResurrectChessProgram();
11430         SendToProgram("analyze\n", &first);
11431         first.analyzing = TRUE;
11432         /*first.maybeThinking = TRUE;*/
11433         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11434         EngineOutputPopUp();
11435     }
11436     gameMode = AnalyzeFile;
11437     pausing = FALSE;
11438     ModeHighlight();
11439     SetGameInfo();
11440
11441     StartAnalysisClock();
11442     GetTimeMark(&lastNodeCountTime);
11443     lastNodeCount = 0;
11444 }
11445
11446 void
11447 MachineWhiteEvent()
11448 {
11449     char buf[MSG_SIZ];
11450     char *bookHit = NULL;
11451
11452     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11453       return;
11454
11455
11456     if (gameMode == PlayFromGameFile || 
11457         gameMode == TwoMachinesPlay  || 
11458         gameMode == Training         || 
11459         gameMode == AnalyzeMode      || 
11460         gameMode == EndOfGame)
11461         EditGameEvent();
11462
11463     if (gameMode == EditPosition) 
11464         EditPositionDone(TRUE);
11465
11466     if (!WhiteOnMove(currentMove)) {
11467         DisplayError(_("It is not White's turn"), 0);
11468         return;
11469     }
11470   
11471     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11472       ExitAnalyzeMode();
11473
11474     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11475         gameMode == AnalyzeFile)
11476         TruncateGame();
11477
11478     ResurrectChessProgram();    /* in case it isn't running */
11479     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11480         gameMode = MachinePlaysWhite;
11481         ResetClocks();
11482     } else
11483     gameMode = MachinePlaysWhite;
11484     pausing = FALSE;
11485     ModeHighlight();
11486     SetGameInfo();
11487     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11488     DisplayTitle(buf);
11489     if (first.sendName) {
11490       sprintf(buf, "name %s\n", gameInfo.black);
11491       SendToProgram(buf, &first);
11492     }
11493     if (first.sendTime) {
11494       if (first.useColors) {
11495         SendToProgram("black\n", &first); /*gnu kludge*/
11496       }
11497       SendTimeRemaining(&first, TRUE);
11498     }
11499     if (first.useColors) {
11500       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11501     }
11502     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11503     SetMachineThinkingEnables();
11504     first.maybeThinking = TRUE;
11505     StartClocks();
11506     firstMove = FALSE;
11507
11508     if (appData.autoFlipView && !flipView) {
11509       flipView = !flipView;
11510       DrawPosition(FALSE, NULL);
11511       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11512     }
11513
11514     if(bookHit) { // [HGM] book: simulate book reply
11515         static char bookMove[MSG_SIZ]; // a bit generous?
11516
11517         programStats.nodes = programStats.depth = programStats.time = 
11518         programStats.score = programStats.got_only_move = 0;
11519         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11520
11521         strcpy(bookMove, "move ");
11522         strcat(bookMove, bookHit);
11523         HandleMachineMove(bookMove, &first);
11524     }
11525 }
11526
11527 void
11528 MachineBlackEvent()
11529 {
11530     char buf[MSG_SIZ];
11531    char *bookHit = NULL;
11532
11533     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11534         return;
11535
11536
11537     if (gameMode == PlayFromGameFile || 
11538         gameMode == TwoMachinesPlay  || 
11539         gameMode == Training         || 
11540         gameMode == AnalyzeMode      || 
11541         gameMode == EndOfGame)
11542         EditGameEvent();
11543
11544     if (gameMode == EditPosition) 
11545         EditPositionDone(TRUE);
11546
11547     if (WhiteOnMove(currentMove)) {
11548         DisplayError(_("It is not Black's turn"), 0);
11549         return;
11550     }
11551     
11552     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11553       ExitAnalyzeMode();
11554
11555     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11556         gameMode == AnalyzeFile)
11557         TruncateGame();
11558
11559     ResurrectChessProgram();    /* in case it isn't running */
11560     gameMode = MachinePlaysBlack;
11561     pausing = FALSE;
11562     ModeHighlight();
11563     SetGameInfo();
11564     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11565     DisplayTitle(buf);
11566     if (first.sendName) {
11567       sprintf(buf, "name %s\n", gameInfo.white);
11568       SendToProgram(buf, &first);
11569     }
11570     if (first.sendTime) {
11571       if (first.useColors) {
11572         SendToProgram("white\n", &first); /*gnu kludge*/
11573       }
11574       SendTimeRemaining(&first, FALSE);
11575     }
11576     if (first.useColors) {
11577       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11578     }
11579     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11580     SetMachineThinkingEnables();
11581     first.maybeThinking = TRUE;
11582     StartClocks();
11583
11584     if (appData.autoFlipView && flipView) {
11585       flipView = !flipView;
11586       DrawPosition(FALSE, NULL);
11587       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11588     }
11589     if(bookHit) { // [HGM] book: simulate book reply
11590         static char bookMove[MSG_SIZ]; // a bit generous?
11591
11592         programStats.nodes = programStats.depth = programStats.time = 
11593         programStats.score = programStats.got_only_move = 0;
11594         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11595
11596         strcpy(bookMove, "move ");
11597         strcat(bookMove, bookHit);
11598         HandleMachineMove(bookMove, &first);
11599     }
11600 }
11601
11602
11603 void
11604 DisplayTwoMachinesTitle()
11605 {
11606     char buf[MSG_SIZ];
11607     if (appData.matchGames > 0) {
11608         if (first.twoMachinesColor[0] == 'w') {
11609             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11610                     gameInfo.white, gameInfo.black,
11611                     first.matchWins, second.matchWins,
11612                     matchGame - 1 - (first.matchWins + second.matchWins));
11613         } else {
11614             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11615                     gameInfo.white, gameInfo.black,
11616                     second.matchWins, first.matchWins,
11617                     matchGame - 1 - (first.matchWins + second.matchWins));
11618         }
11619     } else {
11620         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11621     }
11622     DisplayTitle(buf);
11623 }
11624
11625 void
11626 TwoMachinesEvent P((void))
11627 {
11628     int i;
11629     char buf[MSG_SIZ];
11630     ChessProgramState *onmove;
11631     char *bookHit = NULL;
11632     
11633     if (appData.noChessProgram) return;
11634
11635     switch (gameMode) {
11636       case TwoMachinesPlay:
11637         return;
11638       case MachinePlaysWhite:
11639       case MachinePlaysBlack:
11640         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11641             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11642             return;
11643         }
11644         /* fall through */
11645       case BeginningOfGame:
11646       case PlayFromGameFile:
11647       case EndOfGame:
11648         EditGameEvent();
11649         if (gameMode != EditGame) return;
11650         break;
11651       case EditPosition:
11652         EditPositionDone(TRUE);
11653         break;
11654       case AnalyzeMode:
11655       case AnalyzeFile:
11656         ExitAnalyzeMode();
11657         break;
11658       case EditGame:
11659       default:
11660         break;
11661     }
11662
11663 //    forwardMostMove = currentMove;
11664     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11665     ResurrectChessProgram();    /* in case first program isn't running */
11666
11667     if (second.pr == NULL) {
11668         StartChessProgram(&second);
11669         if (second.protocolVersion == 1) {
11670           TwoMachinesEventIfReady();
11671         } else {
11672           /* kludge: allow timeout for initial "feature" command */
11673           FreezeUI();
11674           DisplayMessage("", _("Starting second chess program"));
11675           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11676         }
11677         return;
11678     }
11679     DisplayMessage("", "");
11680     InitChessProgram(&second, FALSE);
11681     SendToProgram("force\n", &second);
11682     if (startedFromSetupPosition) {
11683         SendBoard(&second, backwardMostMove);
11684     if (appData.debugMode) {
11685         fprintf(debugFP, "Two Machines\n");
11686     }
11687     }
11688     for (i = backwardMostMove; i < forwardMostMove; i++) {
11689         SendMoveToProgram(i, &second);
11690     }
11691
11692     gameMode = TwoMachinesPlay;
11693     pausing = FALSE;
11694     ModeHighlight();
11695     SetGameInfo();
11696     DisplayTwoMachinesTitle();
11697     firstMove = TRUE;
11698     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11699         onmove = &first;
11700     } else {
11701         onmove = &second;
11702     }
11703
11704     SendToProgram(first.computerString, &first);
11705     if (first.sendName) {
11706       sprintf(buf, "name %s\n", second.tidy);
11707       SendToProgram(buf, &first);
11708     }
11709     SendToProgram(second.computerString, &second);
11710     if (second.sendName) {
11711       sprintf(buf, "name %s\n", first.tidy);
11712       SendToProgram(buf, &second);
11713     }
11714
11715     ResetClocks();
11716     if (!first.sendTime || !second.sendTime) {
11717         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11718         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11719     }
11720     if (onmove->sendTime) {
11721       if (onmove->useColors) {
11722         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11723       }
11724       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11725     }
11726     if (onmove->useColors) {
11727       SendToProgram(onmove->twoMachinesColor, onmove);
11728     }
11729     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11730 //    SendToProgram("go\n", onmove);
11731     onmove->maybeThinking = TRUE;
11732     SetMachineThinkingEnables();
11733
11734     StartClocks();
11735
11736     if(bookHit) { // [HGM] book: simulate book reply
11737         static char bookMove[MSG_SIZ]; // a bit generous?
11738
11739         programStats.nodes = programStats.depth = programStats.time = 
11740         programStats.score = programStats.got_only_move = 0;
11741         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11742
11743         strcpy(bookMove, "move ");
11744         strcat(bookMove, bookHit);
11745         savedMessage = bookMove; // args for deferred call
11746         savedState = onmove;
11747         ScheduleDelayedEvent(DeferredBookMove, 1);
11748     }
11749 }
11750
11751 void
11752 TrainingEvent()
11753 {
11754     if (gameMode == Training) {
11755       SetTrainingModeOff();
11756       gameMode = PlayFromGameFile;
11757       DisplayMessage("", _("Training mode off"));
11758     } else {
11759       gameMode = Training;
11760       animateTraining = appData.animate;
11761
11762       /* make sure we are not already at the end of the game */
11763       if (currentMove < forwardMostMove) {
11764         SetTrainingModeOn();
11765         DisplayMessage("", _("Training mode on"));
11766       } else {
11767         gameMode = PlayFromGameFile;
11768         DisplayError(_("Already at end of game"), 0);
11769       }
11770     }
11771     ModeHighlight();
11772 }
11773
11774 void
11775 IcsClientEvent()
11776 {
11777     if (!appData.icsActive) return;
11778     switch (gameMode) {
11779       case IcsPlayingWhite:
11780       case IcsPlayingBlack:
11781       case IcsObserving:
11782       case IcsIdle:
11783       case BeginningOfGame:
11784       case IcsExamining:
11785         return;
11786
11787       case EditGame:
11788         break;
11789
11790       case EditPosition:
11791         EditPositionDone(TRUE);
11792         break;
11793
11794       case AnalyzeMode:
11795       case AnalyzeFile:
11796         ExitAnalyzeMode();
11797         break;
11798         
11799       default:
11800         EditGameEvent();
11801         break;
11802     }
11803
11804     gameMode = IcsIdle;
11805     ModeHighlight();
11806     return;
11807 }
11808
11809
11810 void
11811 EditGameEvent()
11812 {
11813     int i;
11814
11815     switch (gameMode) {
11816       case Training:
11817         SetTrainingModeOff();
11818         break;
11819       case MachinePlaysWhite:
11820       case MachinePlaysBlack:
11821       case BeginningOfGame:
11822         SendToProgram("force\n", &first);
11823         SetUserThinkingEnables();
11824         break;
11825       case PlayFromGameFile:
11826         (void) StopLoadGameTimer();
11827         if (gameFileFP != NULL) {
11828             gameFileFP = NULL;
11829         }
11830         break;
11831       case EditPosition:
11832         EditPositionDone(TRUE);
11833         break;
11834       case AnalyzeMode:
11835       case AnalyzeFile:
11836         ExitAnalyzeMode();
11837         SendToProgram("force\n", &first);
11838         break;
11839       case TwoMachinesPlay:
11840         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11841         ResurrectChessProgram();
11842         SetUserThinkingEnables();
11843         break;
11844       case EndOfGame:
11845         ResurrectChessProgram();
11846         break;
11847       case IcsPlayingBlack:
11848       case IcsPlayingWhite:
11849         DisplayError(_("Warning: You are still playing a game"), 0);
11850         break;
11851       case IcsObserving:
11852         DisplayError(_("Warning: You are still observing a game"), 0);
11853         break;
11854       case IcsExamining:
11855         DisplayError(_("Warning: You are still examining a game"), 0);
11856         break;
11857       case IcsIdle:
11858         break;
11859       case EditGame:
11860       default:
11861         return;
11862     }
11863     
11864     pausing = FALSE;
11865     StopClocks();
11866     first.offeredDraw = second.offeredDraw = 0;
11867
11868     if (gameMode == PlayFromGameFile) {
11869         whiteTimeRemaining = timeRemaining[0][currentMove];
11870         blackTimeRemaining = timeRemaining[1][currentMove];
11871         DisplayTitle("");
11872     }
11873
11874     if (gameMode == MachinePlaysWhite ||
11875         gameMode == MachinePlaysBlack ||
11876         gameMode == TwoMachinesPlay ||
11877         gameMode == EndOfGame) {
11878         i = forwardMostMove;
11879         while (i > currentMove) {
11880             SendToProgram("undo\n", &first);
11881             i--;
11882         }
11883         whiteTimeRemaining = timeRemaining[0][currentMove];
11884         blackTimeRemaining = timeRemaining[1][currentMove];
11885         DisplayBothClocks();
11886         if (whiteFlag || blackFlag) {
11887             whiteFlag = blackFlag = 0;
11888         }
11889         DisplayTitle("");
11890     }           
11891     
11892     gameMode = EditGame;
11893     ModeHighlight();
11894     SetGameInfo();
11895 }
11896
11897
11898 void
11899 EditPositionEvent()
11900 {
11901     if (gameMode == EditPosition) {
11902         EditGameEvent();
11903         return;
11904     }
11905     
11906     EditGameEvent();
11907     if (gameMode != EditGame) return;
11908     
11909     gameMode = EditPosition;
11910     ModeHighlight();
11911     SetGameInfo();
11912     if (currentMove > 0)
11913       CopyBoard(boards[0], boards[currentMove]);
11914     
11915     blackPlaysFirst = !WhiteOnMove(currentMove);
11916     ResetClocks();
11917     currentMove = forwardMostMove = backwardMostMove = 0;
11918     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11919     DisplayMove(-1);
11920 }
11921
11922 void
11923 ExitAnalyzeMode()
11924 {
11925     /* [DM] icsEngineAnalyze - possible call from other functions */
11926     if (appData.icsEngineAnalyze) {
11927         appData.icsEngineAnalyze = FALSE;
11928
11929         DisplayMessage("",_("Close ICS engine analyze..."));
11930     }
11931     if (first.analysisSupport && first.analyzing) {
11932       SendToProgram("exit\n", &first);
11933       first.analyzing = FALSE;
11934     }
11935     thinkOutput[0] = NULLCHAR;
11936 }
11937
11938 void
11939 EditPositionDone(Boolean fakeRights)
11940 {
11941     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11942
11943     startedFromSetupPosition = TRUE;
11944     InitChessProgram(&first, FALSE);
11945     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11946       boards[0][EP_STATUS] = EP_NONE;
11947       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11948     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11949         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11950         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11951       } else boards[0][CASTLING][2] = NoRights;
11952     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11953         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11954         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11955       } else boards[0][CASTLING][5] = NoRights;
11956     }
11957     SendToProgram("force\n", &first);
11958     if (blackPlaysFirst) {
11959         strcpy(moveList[0], "");
11960         strcpy(parseList[0], "");
11961         currentMove = forwardMostMove = backwardMostMove = 1;
11962         CopyBoard(boards[1], boards[0]);
11963     } else {
11964         currentMove = forwardMostMove = backwardMostMove = 0;
11965     }
11966     SendBoard(&first, forwardMostMove);
11967     if (appData.debugMode) {
11968         fprintf(debugFP, "EditPosDone\n");
11969     }
11970     DisplayTitle("");
11971     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11972     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11973     gameMode = EditGame;
11974     ModeHighlight();
11975     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11976     ClearHighlights(); /* [AS] */
11977 }
11978
11979 /* Pause for `ms' milliseconds */
11980 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11981 void
11982 TimeDelay(ms)
11983      long ms;
11984 {
11985     TimeMark m1, m2;
11986
11987     GetTimeMark(&m1);
11988     do {
11989         GetTimeMark(&m2);
11990     } while (SubtractTimeMarks(&m2, &m1) < ms);
11991 }
11992
11993 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11994 void
11995 SendMultiLineToICS(buf)
11996      char *buf;
11997 {
11998     char temp[MSG_SIZ+1], *p;
11999     int len;
12000
12001     len = strlen(buf);
12002     if (len > MSG_SIZ)
12003       len = MSG_SIZ;
12004   
12005     strncpy(temp, buf, len);
12006     temp[len] = 0;
12007
12008     p = temp;
12009     while (*p) {
12010         if (*p == '\n' || *p == '\r')
12011           *p = ' ';
12012         ++p;
12013     }
12014
12015     strcat(temp, "\n");
12016     SendToICS(temp);
12017     SendToPlayer(temp, strlen(temp));
12018 }
12019
12020 void
12021 SetWhiteToPlayEvent()
12022 {
12023     if (gameMode == EditPosition) {
12024         blackPlaysFirst = FALSE;
12025         DisplayBothClocks();    /* works because currentMove is 0 */
12026     } else if (gameMode == IcsExamining) {
12027         SendToICS(ics_prefix);
12028         SendToICS("tomove white\n");
12029     }
12030 }
12031
12032 void
12033 SetBlackToPlayEvent()
12034 {
12035     if (gameMode == EditPosition) {
12036         blackPlaysFirst = TRUE;
12037         currentMove = 1;        /* kludge */
12038         DisplayBothClocks();
12039         currentMove = 0;
12040     } else if (gameMode == IcsExamining) {
12041         SendToICS(ics_prefix);
12042         SendToICS("tomove black\n");
12043     }
12044 }
12045
12046 void
12047 EditPositionMenuEvent(selection, x, y)
12048      ChessSquare selection;
12049      int x, y;
12050 {
12051     char buf[MSG_SIZ];
12052     ChessSquare piece = boards[0][y][x];
12053
12054     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12055
12056     switch (selection) {
12057       case ClearBoard:
12058         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12059             SendToICS(ics_prefix);
12060             SendToICS("bsetup clear\n");
12061         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12062             SendToICS(ics_prefix);
12063             SendToICS("clearboard\n");
12064         } else {
12065             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12066                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12067                 for (y = 0; y < BOARD_HEIGHT; y++) {
12068                     if (gameMode == IcsExamining) {
12069                         if (boards[currentMove][y][x] != EmptySquare) {
12070                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12071                                     AAA + x, ONE + y);
12072                             SendToICS(buf);
12073                         }
12074                     } else {
12075                         boards[0][y][x] = p;
12076                     }
12077                 }
12078             }
12079         }
12080         if (gameMode == EditPosition) {
12081             DrawPosition(FALSE, boards[0]);
12082         }
12083         break;
12084
12085       case WhitePlay:
12086         SetWhiteToPlayEvent();
12087         break;
12088
12089       case BlackPlay:
12090         SetBlackToPlayEvent();
12091         break;
12092
12093       case EmptySquare:
12094         if (gameMode == IcsExamining) {
12095             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12096             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12097             SendToICS(buf);
12098         } else {
12099             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12100                 if(x == BOARD_LEFT-2) {
12101                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12102                     boards[0][y][1] = 0;
12103                 } else
12104                 if(x == BOARD_RGHT+1) {
12105                     if(y >= gameInfo.holdingsSize) break;
12106                     boards[0][y][BOARD_WIDTH-2] = 0;
12107                 } else break;
12108             }
12109             boards[0][y][x] = EmptySquare;
12110             DrawPosition(FALSE, boards[0]);
12111         }
12112         break;
12113
12114       case PromotePiece:
12115         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12116            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12117             selection = (ChessSquare) (PROMOTED piece);
12118         } else if(piece == EmptySquare) selection = WhiteSilver;
12119         else selection = (ChessSquare)((int)piece - 1);
12120         goto defaultlabel;
12121
12122       case DemotePiece:
12123         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12124            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12125             selection = (ChessSquare) (DEMOTED piece);
12126         } else if(piece == EmptySquare) selection = BlackSilver;
12127         else selection = (ChessSquare)((int)piece + 1);       
12128         goto defaultlabel;
12129
12130       case WhiteQueen:
12131       case BlackQueen:
12132         if(gameInfo.variant == VariantShatranj ||
12133            gameInfo.variant == VariantXiangqi  ||
12134            gameInfo.variant == VariantCourier  ||
12135            gameInfo.variant == VariantMakruk     )
12136             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12137         goto defaultlabel;
12138
12139       case WhiteKing:
12140       case BlackKing:
12141         if(gameInfo.variant == VariantXiangqi)
12142             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12143         if(gameInfo.variant == VariantKnightmate)
12144             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12145       default:
12146         defaultlabel:
12147         if (gameMode == IcsExamining) {
12148             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12149             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12150                     PieceToChar(selection), AAA + x, ONE + y);
12151             SendToICS(buf);
12152         } else {
12153             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12154                 int n;
12155                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12156                     n = PieceToNumber(selection - BlackPawn);
12157                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12158                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12159                     boards[0][BOARD_HEIGHT-1-n][1]++;
12160                 } else
12161                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12162                     n = PieceToNumber(selection);
12163                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12164                     boards[0][n][BOARD_WIDTH-1] = selection;
12165                     boards[0][n][BOARD_WIDTH-2]++;
12166                 }
12167             } else
12168             boards[0][y][x] = selection;
12169             DrawPosition(TRUE, boards[0]);
12170         }
12171         break;
12172     }
12173 }
12174
12175
12176 void
12177 DropMenuEvent(selection, x, y)
12178      ChessSquare selection;
12179      int x, y;
12180 {
12181     ChessMove moveType;
12182
12183     switch (gameMode) {
12184       case IcsPlayingWhite:
12185       case MachinePlaysBlack:
12186         if (!WhiteOnMove(currentMove)) {
12187             DisplayMoveError(_("It is Black's turn"));
12188             return;
12189         }
12190         moveType = WhiteDrop;
12191         break;
12192       case IcsPlayingBlack:
12193       case MachinePlaysWhite:
12194         if (WhiteOnMove(currentMove)) {
12195             DisplayMoveError(_("It is White's turn"));
12196             return;
12197         }
12198         moveType = BlackDrop;
12199         break;
12200       case EditGame:
12201         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12202         break;
12203       default:
12204         return;
12205     }
12206
12207     if (moveType == BlackDrop && selection < BlackPawn) {
12208       selection = (ChessSquare) ((int) selection
12209                                  + (int) BlackPawn - (int) WhitePawn);
12210     }
12211     if (boards[currentMove][y][x] != EmptySquare) {
12212         DisplayMoveError(_("That square is occupied"));
12213         return;
12214     }
12215
12216     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12217 }
12218
12219 void
12220 AcceptEvent()
12221 {
12222     /* Accept a pending offer of any kind from opponent */
12223     
12224     if (appData.icsActive) {
12225         SendToICS(ics_prefix);
12226         SendToICS("accept\n");
12227     } else if (cmailMsgLoaded) {
12228         if (currentMove == cmailOldMove &&
12229             commentList[cmailOldMove] != NULL &&
12230             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12231                    "Black offers a draw" : "White offers a draw")) {
12232             TruncateGame();
12233             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12235         } else {
12236             DisplayError(_("There is no pending offer on this move"), 0);
12237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12238         }
12239     } else {
12240         /* Not used for offers from chess program */
12241     }
12242 }
12243
12244 void
12245 DeclineEvent()
12246 {
12247     /* Decline a pending offer of any kind from opponent */
12248     
12249     if (appData.icsActive) {
12250         SendToICS(ics_prefix);
12251         SendToICS("decline\n");
12252     } else if (cmailMsgLoaded) {
12253         if (currentMove == cmailOldMove &&
12254             commentList[cmailOldMove] != NULL &&
12255             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12256                    "Black offers a draw" : "White offers a draw")) {
12257 #ifdef NOTDEF
12258             AppendComment(cmailOldMove, "Draw declined", TRUE);
12259             DisplayComment(cmailOldMove - 1, "Draw declined");
12260 #endif /*NOTDEF*/
12261         } else {
12262             DisplayError(_("There is no pending offer on this move"), 0);
12263         }
12264     } else {
12265         /* Not used for offers from chess program */
12266     }
12267 }
12268
12269 void
12270 RematchEvent()
12271 {
12272     /* Issue ICS rematch command */
12273     if (appData.icsActive) {
12274         SendToICS(ics_prefix);
12275         SendToICS("rematch\n");
12276     }
12277 }
12278
12279 void
12280 CallFlagEvent()
12281 {
12282     /* Call your opponent's flag (claim a win on time) */
12283     if (appData.icsActive) {
12284         SendToICS(ics_prefix);
12285         SendToICS("flag\n");
12286     } else {
12287         switch (gameMode) {
12288           default:
12289             return;
12290           case MachinePlaysWhite:
12291             if (whiteFlag) {
12292                 if (blackFlag)
12293                   GameEnds(GameIsDrawn, "Both players ran out of time",
12294                            GE_PLAYER);
12295                 else
12296                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12297             } else {
12298                 DisplayError(_("Your opponent is not out of time"), 0);
12299             }
12300             break;
12301           case MachinePlaysBlack:
12302             if (blackFlag) {
12303                 if (whiteFlag)
12304                   GameEnds(GameIsDrawn, "Both players ran out of time",
12305                            GE_PLAYER);
12306                 else
12307                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12308             } else {
12309                 DisplayError(_("Your opponent is not out of time"), 0);
12310             }
12311             break;
12312         }
12313     }
12314 }
12315
12316 void
12317 DrawEvent()
12318 {
12319     /* Offer draw or accept pending draw offer from opponent */
12320     
12321     if (appData.icsActive) {
12322         /* Note: tournament rules require draw offers to be
12323            made after you make your move but before you punch
12324            your clock.  Currently ICS doesn't let you do that;
12325            instead, you immediately punch your clock after making
12326            a move, but you can offer a draw at any time. */
12327         
12328         SendToICS(ics_prefix);
12329         SendToICS("draw\n");
12330         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12331     } else if (cmailMsgLoaded) {
12332         if (currentMove == cmailOldMove &&
12333             commentList[cmailOldMove] != NULL &&
12334             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12335                    "Black offers a draw" : "White offers a draw")) {
12336             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12337             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12338         } else if (currentMove == cmailOldMove + 1) {
12339             char *offer = WhiteOnMove(cmailOldMove) ?
12340               "White offers a draw" : "Black offers a draw";
12341             AppendComment(currentMove, offer, TRUE);
12342             DisplayComment(currentMove - 1, offer);
12343             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12344         } else {
12345             DisplayError(_("You must make your move before offering a draw"), 0);
12346             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12347         }
12348     } else if (first.offeredDraw) {
12349         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12350     } else {
12351         if (first.sendDrawOffers) {
12352             SendToProgram("draw\n", &first);
12353             userOfferedDraw = TRUE;
12354         }
12355     }
12356 }
12357
12358 void
12359 AdjournEvent()
12360 {
12361     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12362     
12363     if (appData.icsActive) {
12364         SendToICS(ics_prefix);
12365         SendToICS("adjourn\n");
12366     } else {
12367         /* Currently GNU Chess doesn't offer or accept Adjourns */
12368     }
12369 }
12370
12371
12372 void
12373 AbortEvent()
12374 {
12375     /* Offer Abort or accept pending Abort offer from opponent */
12376     
12377     if (appData.icsActive) {
12378         SendToICS(ics_prefix);
12379         SendToICS("abort\n");
12380     } else {
12381         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12382     }
12383 }
12384
12385 void
12386 ResignEvent()
12387 {
12388     /* Resign.  You can do this even if it's not your turn. */
12389     
12390     if (appData.icsActive) {
12391         SendToICS(ics_prefix);
12392         SendToICS("resign\n");
12393     } else {
12394         switch (gameMode) {
12395           case MachinePlaysWhite:
12396             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12397             break;
12398           case MachinePlaysBlack:
12399             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12400             break;
12401           case EditGame:
12402             if (cmailMsgLoaded) {
12403                 TruncateGame();
12404                 if (WhiteOnMove(cmailOldMove)) {
12405                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12406                 } else {
12407                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12408                 }
12409                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12410             }
12411             break;
12412           default:
12413             break;
12414         }
12415     }
12416 }
12417
12418
12419 void
12420 StopObservingEvent()
12421 {
12422     /* Stop observing current games */
12423     SendToICS(ics_prefix);
12424     SendToICS("unobserve\n");
12425 }
12426
12427 void
12428 StopExaminingEvent()
12429 {
12430     /* Stop observing current game */
12431     SendToICS(ics_prefix);
12432     SendToICS("unexamine\n");
12433 }
12434
12435 void
12436 ForwardInner(target)
12437      int target;
12438 {
12439     int limit;
12440
12441     if (appData.debugMode)
12442         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12443                 target, currentMove, forwardMostMove);
12444
12445     if (gameMode == EditPosition)
12446       return;
12447
12448     if (gameMode == PlayFromGameFile && !pausing)
12449       PauseEvent();
12450     
12451     if (gameMode == IcsExamining && pausing)
12452       limit = pauseExamForwardMostMove;
12453     else
12454       limit = forwardMostMove;
12455     
12456     if (target > limit) target = limit;
12457
12458     if (target > 0 && moveList[target - 1][0]) {
12459         int fromX, fromY, toX, toY;
12460         toX = moveList[target - 1][2] - AAA;
12461         toY = moveList[target - 1][3] - ONE;
12462         if (moveList[target - 1][1] == '@') {
12463             if (appData.highlightLastMove) {
12464                 SetHighlights(-1, -1, toX, toY);
12465             }
12466         } else {
12467             fromX = moveList[target - 1][0] - AAA;
12468             fromY = moveList[target - 1][1] - ONE;
12469             if (target == currentMove + 1) {
12470                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12471             }
12472             if (appData.highlightLastMove) {
12473                 SetHighlights(fromX, fromY, toX, toY);
12474             }
12475         }
12476     }
12477     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12478         gameMode == Training || gameMode == PlayFromGameFile || 
12479         gameMode == AnalyzeFile) {
12480         while (currentMove < target) {
12481             SendMoveToProgram(currentMove++, &first);
12482         }
12483     } else {
12484         currentMove = target;
12485     }
12486     
12487     if (gameMode == EditGame || gameMode == EndOfGame) {
12488         whiteTimeRemaining = timeRemaining[0][currentMove];
12489         blackTimeRemaining = timeRemaining[1][currentMove];
12490     }
12491     DisplayBothClocks();
12492     DisplayMove(currentMove - 1);
12493     DrawPosition(FALSE, boards[currentMove]);
12494     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12495     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12496         DisplayComment(currentMove - 1, commentList[currentMove]);
12497     }
12498 }
12499
12500
12501 void
12502 ForwardEvent()
12503 {
12504     if (gameMode == IcsExamining && !pausing) {
12505         SendToICS(ics_prefix);
12506         SendToICS("forward\n");
12507     } else {
12508         ForwardInner(currentMove + 1);
12509     }
12510 }
12511
12512 void
12513 ToEndEvent()
12514 {
12515     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12516         /* to optimze, we temporarily turn off analysis mode while we feed
12517          * the remaining moves to the engine. Otherwise we get analysis output
12518          * after each move.
12519          */ 
12520         if (first.analysisSupport) {
12521           SendToProgram("exit\nforce\n", &first);
12522           first.analyzing = FALSE;
12523         }
12524     }
12525         
12526     if (gameMode == IcsExamining && !pausing) {
12527         SendToICS(ics_prefix);
12528         SendToICS("forward 999999\n");
12529     } else {
12530         ForwardInner(forwardMostMove);
12531     }
12532
12533     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12534         /* we have fed all the moves, so reactivate analysis mode */
12535         SendToProgram("analyze\n", &first);
12536         first.analyzing = TRUE;
12537         /*first.maybeThinking = TRUE;*/
12538         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12539     }
12540 }
12541
12542 void
12543 BackwardInner(target)
12544      int target;
12545 {
12546     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12547
12548     if (appData.debugMode)
12549         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12550                 target, currentMove, forwardMostMove);
12551
12552     if (gameMode == EditPosition) return;
12553     if (currentMove <= backwardMostMove) {
12554         ClearHighlights();
12555         DrawPosition(full_redraw, boards[currentMove]);
12556         return;
12557     }
12558     if (gameMode == PlayFromGameFile && !pausing)
12559       PauseEvent();
12560     
12561     if (moveList[target][0]) {
12562         int fromX, fromY, toX, toY;
12563         toX = moveList[target][2] - AAA;
12564         toY = moveList[target][3] - ONE;
12565         if (moveList[target][1] == '@') {
12566             if (appData.highlightLastMove) {
12567                 SetHighlights(-1, -1, toX, toY);
12568             }
12569         } else {
12570             fromX = moveList[target][0] - AAA;
12571             fromY = moveList[target][1] - ONE;
12572             if (target == currentMove - 1) {
12573                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12574             }
12575             if (appData.highlightLastMove) {
12576                 SetHighlights(fromX, fromY, toX, toY);
12577             }
12578         }
12579     }
12580     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12581         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12582         while (currentMove > target) {
12583             SendToProgram("undo\n", &first);
12584             currentMove--;
12585         }
12586     } else {
12587         currentMove = target;
12588     }
12589     
12590     if (gameMode == EditGame || gameMode == EndOfGame) {
12591         whiteTimeRemaining = timeRemaining[0][currentMove];
12592         blackTimeRemaining = timeRemaining[1][currentMove];
12593     }
12594     DisplayBothClocks();
12595     DisplayMove(currentMove - 1);
12596     DrawPosition(full_redraw, boards[currentMove]);
12597     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12598     // [HGM] PV info: routine tests if comment empty
12599     DisplayComment(currentMove - 1, commentList[currentMove]);
12600 }
12601
12602 void
12603 BackwardEvent()
12604 {
12605     if (gameMode == IcsExamining && !pausing) {
12606         SendToICS(ics_prefix);
12607         SendToICS("backward\n");
12608     } else {
12609         BackwardInner(currentMove - 1);
12610     }
12611 }
12612
12613 void
12614 ToStartEvent()
12615 {
12616     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12617         /* to optimize, we temporarily turn off analysis mode while we undo
12618          * all the moves. Otherwise we get analysis output after each undo.
12619          */ 
12620         if (first.analysisSupport) {
12621           SendToProgram("exit\nforce\n", &first);
12622           first.analyzing = FALSE;
12623         }
12624     }
12625
12626     if (gameMode == IcsExamining && !pausing) {
12627         SendToICS(ics_prefix);
12628         SendToICS("backward 999999\n");
12629     } else {
12630         BackwardInner(backwardMostMove);
12631     }
12632
12633     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12634         /* we have fed all the moves, so reactivate analysis mode */
12635         SendToProgram("analyze\n", &first);
12636         first.analyzing = TRUE;
12637         /*first.maybeThinking = TRUE;*/
12638         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12639     }
12640 }
12641
12642 void
12643 ToNrEvent(int to)
12644 {
12645   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12646   if (to >= forwardMostMove) to = forwardMostMove;
12647   if (to <= backwardMostMove) to = backwardMostMove;
12648   if (to < currentMove) {
12649     BackwardInner(to);
12650   } else {
12651     ForwardInner(to);
12652   }
12653 }
12654
12655 void
12656 RevertEvent()
12657 {
12658     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12659         return;
12660     }
12661     if (gameMode != IcsExamining) {
12662         DisplayError(_("You are not examining a game"), 0);
12663         return;
12664     }
12665     if (pausing) {
12666         DisplayError(_("You can't revert while pausing"), 0);
12667         return;
12668     }
12669     SendToICS(ics_prefix);
12670     SendToICS("revert\n");
12671 }
12672
12673 void
12674 RetractMoveEvent()
12675 {
12676     switch (gameMode) {
12677       case MachinePlaysWhite:
12678       case MachinePlaysBlack:
12679         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12680             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12681             return;
12682         }
12683         if (forwardMostMove < 2) return;
12684         currentMove = forwardMostMove = forwardMostMove - 2;
12685         whiteTimeRemaining = timeRemaining[0][currentMove];
12686         blackTimeRemaining = timeRemaining[1][currentMove];
12687         DisplayBothClocks();
12688         DisplayMove(currentMove - 1);
12689         ClearHighlights();/*!! could figure this out*/
12690         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12691         SendToProgram("remove\n", &first);
12692         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12693         break;
12694
12695       case BeginningOfGame:
12696       default:
12697         break;
12698
12699       case IcsPlayingWhite:
12700       case IcsPlayingBlack:
12701         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12702             SendToICS(ics_prefix);
12703             SendToICS("takeback 2\n");
12704         } else {
12705             SendToICS(ics_prefix);
12706             SendToICS("takeback 1\n");
12707         }
12708         break;
12709     }
12710 }
12711
12712 void
12713 MoveNowEvent()
12714 {
12715     ChessProgramState *cps;
12716
12717     switch (gameMode) {
12718       case MachinePlaysWhite:
12719         if (!WhiteOnMove(forwardMostMove)) {
12720             DisplayError(_("It is your turn"), 0);
12721             return;
12722         }
12723         cps = &first;
12724         break;
12725       case MachinePlaysBlack:
12726         if (WhiteOnMove(forwardMostMove)) {
12727             DisplayError(_("It is your turn"), 0);
12728             return;
12729         }
12730         cps = &first;
12731         break;
12732       case TwoMachinesPlay:
12733         if (WhiteOnMove(forwardMostMove) ==
12734             (first.twoMachinesColor[0] == 'w')) {
12735             cps = &first;
12736         } else {
12737             cps = &second;
12738         }
12739         break;
12740       case BeginningOfGame:
12741       default:
12742         return;
12743     }
12744     SendToProgram("?\n", cps);
12745 }
12746
12747 void
12748 TruncateGameEvent()
12749 {
12750     EditGameEvent();
12751     if (gameMode != EditGame) return;
12752     TruncateGame();
12753 }
12754
12755 void
12756 TruncateGame()
12757 {
12758     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12759     if (forwardMostMove > currentMove) {
12760         if (gameInfo.resultDetails != NULL) {
12761             free(gameInfo.resultDetails);
12762             gameInfo.resultDetails = NULL;
12763             gameInfo.result = GameUnfinished;
12764         }
12765         forwardMostMove = currentMove;
12766         HistorySet(parseList, backwardMostMove, forwardMostMove,
12767                    currentMove-1);
12768     }
12769 }
12770
12771 void
12772 HintEvent()
12773 {
12774     if (appData.noChessProgram) return;
12775     switch (gameMode) {
12776       case MachinePlaysWhite:
12777         if (WhiteOnMove(forwardMostMove)) {
12778             DisplayError(_("Wait until your turn"), 0);
12779             return;
12780         }
12781         break;
12782       case BeginningOfGame:
12783       case MachinePlaysBlack:
12784         if (!WhiteOnMove(forwardMostMove)) {
12785             DisplayError(_("Wait until your turn"), 0);
12786             return;
12787         }
12788         break;
12789       default:
12790         DisplayError(_("No hint available"), 0);
12791         return;
12792     }
12793     SendToProgram("hint\n", &first);
12794     hintRequested = TRUE;
12795 }
12796
12797 void
12798 BookEvent()
12799 {
12800     if (appData.noChessProgram) return;
12801     switch (gameMode) {
12802       case MachinePlaysWhite:
12803         if (WhiteOnMove(forwardMostMove)) {
12804             DisplayError(_("Wait until your turn"), 0);
12805             return;
12806         }
12807         break;
12808       case BeginningOfGame:
12809       case MachinePlaysBlack:
12810         if (!WhiteOnMove(forwardMostMove)) {
12811             DisplayError(_("Wait until your turn"), 0);
12812             return;
12813         }
12814         break;
12815       case EditPosition:
12816         EditPositionDone(TRUE);
12817         break;
12818       case TwoMachinesPlay:
12819         return;
12820       default:
12821         break;
12822     }
12823     SendToProgram("bk\n", &first);
12824     bookOutput[0] = NULLCHAR;
12825     bookRequested = TRUE;
12826 }
12827
12828 void
12829 AboutGameEvent()
12830 {
12831     char *tags = PGNTags(&gameInfo);
12832     TagsPopUp(tags, CmailMsg());
12833     free(tags);
12834 }
12835
12836 /* end button procedures */
12837
12838 void
12839 PrintPosition(fp, move)
12840      FILE *fp;
12841      int move;
12842 {
12843     int i, j;
12844     
12845     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12846         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12847             char c = PieceToChar(boards[move][i][j]);
12848             fputc(c == 'x' ? '.' : c, fp);
12849             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12850         }
12851     }
12852     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12853       fprintf(fp, "white to play\n");
12854     else
12855       fprintf(fp, "black to play\n");
12856 }
12857
12858 void
12859 PrintOpponents(fp)
12860      FILE *fp;
12861 {
12862     if (gameInfo.white != NULL) {
12863         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12864     } else {
12865         fprintf(fp, "\n");
12866     }
12867 }
12868
12869 /* Find last component of program's own name, using some heuristics */
12870 void
12871 TidyProgramName(prog, host, buf)
12872      char *prog, *host, buf[MSG_SIZ];
12873 {
12874     char *p, *q;
12875     int local = (strcmp(host, "localhost") == 0);
12876     while (!local && (p = strchr(prog, ';')) != NULL) {
12877         p++;
12878         while (*p == ' ') p++;
12879         prog = p;
12880     }
12881     if (*prog == '"' || *prog == '\'') {
12882         q = strchr(prog + 1, *prog);
12883     } else {
12884         q = strchr(prog, ' ');
12885     }
12886     if (q == NULL) q = prog + strlen(prog);
12887     p = q;
12888     while (p >= prog && *p != '/' && *p != '\\') p--;
12889     p++;
12890     if(p == prog && *p == '"') p++;
12891     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12892     memcpy(buf, p, q - p);
12893     buf[q - p] = NULLCHAR;
12894     if (!local) {
12895         strcat(buf, "@");
12896         strcat(buf, host);
12897     }
12898 }
12899
12900 char *
12901 TimeControlTagValue()
12902 {
12903     char buf[MSG_SIZ];
12904     if (!appData.clockMode) {
12905         strcpy(buf, "-");
12906     } else if (movesPerSession > 0) {
12907         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12908     } else if (timeIncrement == 0) {
12909         sprintf(buf, "%ld", timeControl/1000);
12910     } else {
12911         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12912     }
12913     return StrSave(buf);
12914 }
12915
12916 void
12917 SetGameInfo()
12918 {
12919     /* This routine is used only for certain modes */
12920     VariantClass v = gameInfo.variant;
12921     ChessMove r = GameUnfinished;
12922     char *p = NULL;
12923
12924     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12925         r = gameInfo.result; 
12926         p = gameInfo.resultDetails; 
12927         gameInfo.resultDetails = NULL;
12928     }
12929     ClearGameInfo(&gameInfo);
12930     gameInfo.variant = v;
12931
12932     switch (gameMode) {
12933       case MachinePlaysWhite:
12934         gameInfo.event = StrSave( appData.pgnEventHeader );
12935         gameInfo.site = StrSave(HostName());
12936         gameInfo.date = PGNDate();
12937         gameInfo.round = StrSave("-");
12938         gameInfo.white = StrSave(first.tidy);
12939         gameInfo.black = StrSave(UserName());
12940         gameInfo.timeControl = TimeControlTagValue();
12941         break;
12942
12943       case MachinePlaysBlack:
12944         gameInfo.event = StrSave( appData.pgnEventHeader );
12945         gameInfo.site = StrSave(HostName());
12946         gameInfo.date = PGNDate();
12947         gameInfo.round = StrSave("-");
12948         gameInfo.white = StrSave(UserName());
12949         gameInfo.black = StrSave(first.tidy);
12950         gameInfo.timeControl = TimeControlTagValue();
12951         break;
12952
12953       case TwoMachinesPlay:
12954         gameInfo.event = StrSave( appData.pgnEventHeader );
12955         gameInfo.site = StrSave(HostName());
12956         gameInfo.date = PGNDate();
12957         if (matchGame > 0) {
12958             char buf[MSG_SIZ];
12959             sprintf(buf, "%d", matchGame);
12960             gameInfo.round = StrSave(buf);
12961         } else {
12962             gameInfo.round = StrSave("-");
12963         }
12964         if (first.twoMachinesColor[0] == 'w') {
12965             gameInfo.white = StrSave(first.tidy);
12966             gameInfo.black = StrSave(second.tidy);
12967         } else {
12968             gameInfo.white = StrSave(second.tidy);
12969             gameInfo.black = StrSave(first.tidy);
12970         }
12971         gameInfo.timeControl = TimeControlTagValue();
12972         break;
12973
12974       case EditGame:
12975         gameInfo.event = StrSave("Edited game");
12976         gameInfo.site = StrSave(HostName());
12977         gameInfo.date = PGNDate();
12978         gameInfo.round = StrSave("-");
12979         gameInfo.white = StrSave("-");
12980         gameInfo.black = StrSave("-");
12981         gameInfo.result = r;
12982         gameInfo.resultDetails = p;
12983         break;
12984
12985       case EditPosition:
12986         gameInfo.event = StrSave("Edited position");
12987         gameInfo.site = StrSave(HostName());
12988         gameInfo.date = PGNDate();
12989         gameInfo.round = StrSave("-");
12990         gameInfo.white = StrSave("-");
12991         gameInfo.black = StrSave("-");
12992         break;
12993
12994       case IcsPlayingWhite:
12995       case IcsPlayingBlack:
12996       case IcsObserving:
12997       case IcsExamining:
12998         break;
12999
13000       case PlayFromGameFile:
13001         gameInfo.event = StrSave("Game from non-PGN file");
13002         gameInfo.site = StrSave(HostName());
13003         gameInfo.date = PGNDate();
13004         gameInfo.round = StrSave("-");
13005         gameInfo.white = StrSave("?");
13006         gameInfo.black = StrSave("?");
13007         break;
13008
13009       default:
13010         break;
13011     }
13012 }
13013
13014 void
13015 ReplaceComment(index, text)
13016      int index;
13017      char *text;
13018 {
13019     int len;
13020
13021     while (*text == '\n') text++;
13022     len = strlen(text);
13023     while (len > 0 && text[len - 1] == '\n') len--;
13024
13025     if (commentList[index] != NULL)
13026       free(commentList[index]);
13027
13028     if (len == 0) {
13029         commentList[index] = NULL;
13030         return;
13031     }
13032   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13033       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13034       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13035     commentList[index] = (char *) malloc(len + 2);
13036     strncpy(commentList[index], text, len);
13037     commentList[index][len] = '\n';
13038     commentList[index][len + 1] = NULLCHAR;
13039   } else { 
13040     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13041     char *p;
13042     commentList[index] = (char *) malloc(len + 6);
13043     strcpy(commentList[index], "{\n");
13044     strncpy(commentList[index]+2, text, len);
13045     commentList[index][len+2] = NULLCHAR;
13046     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13047     strcat(commentList[index], "\n}\n");
13048   }
13049 }
13050
13051 void
13052 CrushCRs(text)
13053      char *text;
13054 {
13055   char *p = text;
13056   char *q = text;
13057   char ch;
13058
13059   do {
13060     ch = *p++;
13061     if (ch == '\r') continue;
13062     *q++ = ch;
13063   } while (ch != '\0');
13064 }
13065
13066 void
13067 AppendComment(index, text, addBraces)
13068      int index;
13069      char *text;
13070      Boolean addBraces; // [HGM] braces: tells if we should add {}
13071 {
13072     int oldlen, len;
13073     char *old;
13074
13075 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13076     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13077
13078     CrushCRs(text);
13079     while (*text == '\n') text++;
13080     len = strlen(text);
13081     while (len > 0 && text[len - 1] == '\n') len--;
13082
13083     if (len == 0) return;
13084
13085     if (commentList[index] != NULL) {
13086         old = commentList[index];
13087         oldlen = strlen(old);
13088         while(commentList[index][oldlen-1] ==  '\n')
13089           commentList[index][--oldlen] = NULLCHAR;
13090         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13091         strcpy(commentList[index], old);
13092         free(old);
13093         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13094         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13095           if(addBraces) addBraces = FALSE; else { text++; len--; }
13096           while (*text == '\n') { text++; len--; }
13097           commentList[index][--oldlen] = NULLCHAR;
13098       }
13099         if(addBraces) strcat(commentList[index], "\n{\n");
13100         else          strcat(commentList[index], "\n");
13101         strcat(commentList[index], text);
13102         if(addBraces) strcat(commentList[index], "\n}\n");
13103         else          strcat(commentList[index], "\n");
13104     } else {
13105         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13106         if(addBraces)
13107              strcpy(commentList[index], "{\n");
13108         else commentList[index][0] = NULLCHAR;
13109         strcat(commentList[index], text);
13110         strcat(commentList[index], "\n");
13111         if(addBraces) strcat(commentList[index], "}\n");
13112     }
13113 }
13114
13115 static char * FindStr( char * text, char * sub_text )
13116 {
13117     char * result = strstr( text, sub_text );
13118
13119     if( result != NULL ) {
13120         result += strlen( sub_text );
13121     }
13122
13123     return result;
13124 }
13125
13126 /* [AS] Try to extract PV info from PGN comment */
13127 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13128 char *GetInfoFromComment( int index, char * text )
13129 {
13130     char * sep = text;
13131
13132     if( text != NULL && index > 0 ) {
13133         int score = 0;
13134         int depth = 0;
13135         int time = -1, sec = 0, deci;
13136         char * s_eval = FindStr( text, "[%eval " );
13137         char * s_emt = FindStr( text, "[%emt " );
13138
13139         if( s_eval != NULL || s_emt != NULL ) {
13140             /* New style */
13141             char delim;
13142
13143             if( s_eval != NULL ) {
13144                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13145                     return text;
13146                 }
13147
13148                 if( delim != ']' ) {
13149                     return text;
13150                 }
13151             }
13152
13153             if( s_emt != NULL ) {
13154             }
13155                 return text;
13156         }
13157         else {
13158             /* We expect something like: [+|-]nnn.nn/dd */
13159             int score_lo = 0;
13160
13161             if(*text != '{') return text; // [HGM] braces: must be normal comment
13162
13163             sep = strchr( text, '/' );
13164             if( sep == NULL || sep < (text+4) ) {
13165                 return text;
13166             }
13167
13168             time = -1; sec = -1; deci = -1;
13169             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13170                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13171                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13172                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13173                 return text;
13174             }
13175
13176             if( score_lo < 0 || score_lo >= 100 ) {
13177                 return text;
13178             }
13179
13180             if(sec >= 0) time = 600*time + 10*sec; else
13181             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13182
13183             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13184
13185             /* [HGM] PV time: now locate end of PV info */
13186             while( *++sep >= '0' && *sep <= '9'); // strip depth
13187             if(time >= 0)
13188             while( *++sep >= '0' && *sep <= '9'); // strip time
13189             if(sec >= 0)
13190             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13191             if(deci >= 0)
13192             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13193             while(*sep == ' ') sep++;
13194         }
13195
13196         if( depth <= 0 ) {
13197             return text;
13198         }
13199
13200         if( time < 0 ) {
13201             time = -1;
13202         }
13203
13204         pvInfoList[index-1].depth = depth;
13205         pvInfoList[index-1].score = score;
13206         pvInfoList[index-1].time  = 10*time; // centi-sec
13207         if(*sep == '}') *sep = 0; else *--sep = '{';
13208     }
13209     return sep;
13210 }
13211
13212 void
13213 SendToProgram(message, cps)
13214      char *message;
13215      ChessProgramState *cps;
13216 {
13217     int count, outCount, error;
13218     char buf[MSG_SIZ];
13219
13220     if (cps->pr == NULL) return;
13221     Attention(cps);
13222     
13223     if (appData.debugMode) {
13224         TimeMark now;
13225         GetTimeMark(&now);
13226         fprintf(debugFP, "%ld >%-6s: %s", 
13227                 SubtractTimeMarks(&now, &programStartTime),
13228                 cps->which, message);
13229     }
13230     
13231     count = strlen(message);
13232     outCount = OutputToProcess(cps->pr, message, count, &error);
13233     if (outCount < count && !exiting 
13234                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13235         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13236         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13237             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13238                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13239                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13240             } else {
13241                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13242             }
13243             gameInfo.resultDetails = StrSave(buf);
13244         }
13245         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13246     }
13247 }
13248
13249 void
13250 ReceiveFromProgram(isr, closure, message, count, error)
13251      InputSourceRef isr;
13252      VOIDSTAR closure;
13253      char *message;
13254      int count;
13255      int error;
13256 {
13257     char *end_str;
13258     char buf[MSG_SIZ];
13259     ChessProgramState *cps = (ChessProgramState *)closure;
13260
13261     if (isr != cps->isr) return; /* Killed intentionally */
13262     if (count <= 0) {
13263         if (count == 0) {
13264             sprintf(buf,
13265                     _("Error: %s chess program (%s) exited unexpectedly"),
13266                     cps->which, cps->program);
13267         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13268                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13269                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13270                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13271                 } else {
13272                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13273                 }
13274                 gameInfo.resultDetails = StrSave(buf);
13275             }
13276             RemoveInputSource(cps->isr);
13277             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13278         } else {
13279             sprintf(buf,
13280                     _("Error reading from %s chess program (%s)"),
13281                     cps->which, cps->program);
13282             RemoveInputSource(cps->isr);
13283
13284             /* [AS] Program is misbehaving badly... kill it */
13285             if( count == -2 ) {
13286                 DestroyChildProcess( cps->pr, 9 );
13287                 cps->pr = NoProc;
13288             }
13289
13290             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13291         }
13292         return;
13293     }
13294     
13295     if ((end_str = strchr(message, '\r')) != NULL)
13296       *end_str = NULLCHAR;
13297     if ((end_str = strchr(message, '\n')) != NULL)
13298       *end_str = NULLCHAR;
13299     
13300     if (appData.debugMode) {
13301         TimeMark now; int print = 1;
13302         char *quote = ""; char c; int i;
13303
13304         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13305                 char start = message[0];
13306                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13307                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13308                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13309                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13310                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13311                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13312                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13313                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13314                         { quote = "# "; print = (appData.engineComments == 2); }
13315                 message[0] = start; // restore original message
13316         }
13317         if(print) {
13318                 GetTimeMark(&now);
13319                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13320                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13321                         quote,
13322                         message);
13323         }
13324     }
13325
13326     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13327     if (appData.icsEngineAnalyze) {
13328         if (strstr(message, "whisper") != NULL ||
13329              strstr(message, "kibitz") != NULL || 
13330             strstr(message, "tellics") != NULL) return;
13331     }
13332
13333     HandleMachineMove(message, cps);
13334 }
13335
13336
13337 void
13338 SendTimeControl(cps, mps, tc, inc, sd, st)
13339      ChessProgramState *cps;
13340      int mps, inc, sd, st;
13341      long tc;
13342 {
13343     char buf[MSG_SIZ];
13344     int seconds;
13345
13346     if( timeControl_2 > 0 ) {
13347         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13348             tc = timeControl_2;
13349         }
13350     }
13351     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13352     inc /= cps->timeOdds;
13353     st  /= cps->timeOdds;
13354
13355     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13356
13357     if (st > 0) {
13358       /* Set exact time per move, normally using st command */
13359       if (cps->stKludge) {
13360         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13361         seconds = st % 60;
13362         if (seconds == 0) {
13363           sprintf(buf, "level 1 %d\n", st/60);
13364         } else {
13365           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13366         }
13367       } else {
13368         sprintf(buf, "st %d\n", st);
13369       }
13370     } else {
13371       /* Set conventional or incremental time control, using level command */
13372       if (seconds == 0) {
13373         /* Note old gnuchess bug -- minutes:seconds used to not work.
13374            Fixed in later versions, but still avoid :seconds
13375            when seconds is 0. */
13376         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13377       } else {
13378         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13379                 seconds, inc/1000);
13380       }
13381     }
13382     SendToProgram(buf, cps);
13383
13384     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13385     /* Orthogonally, limit search to given depth */
13386     if (sd > 0) {
13387       if (cps->sdKludge) {
13388         sprintf(buf, "depth\n%d\n", sd);
13389       } else {
13390         sprintf(buf, "sd %d\n", sd);
13391       }
13392       SendToProgram(buf, cps);
13393     }
13394
13395     if(cps->nps > 0) { /* [HGM] nps */
13396         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13397         else {
13398                 sprintf(buf, "nps %d\n", cps->nps);
13399               SendToProgram(buf, cps);
13400         }
13401     }
13402 }
13403
13404 ChessProgramState *WhitePlayer()
13405 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13406 {
13407     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13408        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13409         return &second;
13410     return &first;
13411 }
13412
13413 void
13414 SendTimeRemaining(cps, machineWhite)
13415      ChessProgramState *cps;
13416      int /*boolean*/ machineWhite;
13417 {
13418     char message[MSG_SIZ];
13419     long time, otime;
13420
13421     /* Note: this routine must be called when the clocks are stopped
13422        or when they have *just* been set or switched; otherwise
13423        it will be off by the time since the current tick started.
13424     */
13425     if (machineWhite) {
13426         time = whiteTimeRemaining / 10;
13427         otime = blackTimeRemaining / 10;
13428     } else {
13429         time = blackTimeRemaining / 10;
13430         otime = whiteTimeRemaining / 10;
13431     }
13432     /* [HGM] translate opponent's time by time-odds factor */
13433     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13434     if (appData.debugMode) {
13435         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13436     }
13437
13438     if (time <= 0) time = 1;
13439     if (otime <= 0) otime = 1;
13440     
13441     sprintf(message, "time %ld\n", time);
13442     SendToProgram(message, cps);
13443
13444     sprintf(message, "otim %ld\n", otime);
13445     SendToProgram(message, cps);
13446 }
13447
13448 int
13449 BoolFeature(p, name, loc, cps)
13450      char **p;
13451      char *name;
13452      int *loc;
13453      ChessProgramState *cps;
13454 {
13455   char buf[MSG_SIZ];
13456   int len = strlen(name);
13457   int val;
13458   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13459     (*p) += len + 1;
13460     sscanf(*p, "%d", &val);
13461     *loc = (val != 0);
13462     while (**p && **p != ' ') (*p)++;
13463     sprintf(buf, "accepted %s\n", name);
13464     SendToProgram(buf, cps);
13465     return TRUE;
13466   }
13467   return FALSE;
13468 }
13469
13470 int
13471 IntFeature(p, name, loc, cps)
13472      char **p;
13473      char *name;
13474      int *loc;
13475      ChessProgramState *cps;
13476 {
13477   char buf[MSG_SIZ];
13478   int len = strlen(name);
13479   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13480     (*p) += len + 1;
13481     sscanf(*p, "%d", loc);
13482     while (**p && **p != ' ') (*p)++;
13483     sprintf(buf, "accepted %s\n", name);
13484     SendToProgram(buf, cps);
13485     return TRUE;
13486   }
13487   return FALSE;
13488 }
13489
13490 int
13491 StringFeature(p, name, loc, cps)
13492      char **p;
13493      char *name;
13494      char loc[];
13495      ChessProgramState *cps;
13496 {
13497   char buf[MSG_SIZ];
13498   int len = strlen(name);
13499   if (strncmp((*p), name, len) == 0
13500       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13501     (*p) += len + 2;
13502     sscanf(*p, "%[^\"]", loc);
13503     while (**p && **p != '\"') (*p)++;
13504     if (**p == '\"') (*p)++;
13505     sprintf(buf, "accepted %s\n", name);
13506     SendToProgram(buf, cps);
13507     return TRUE;
13508   }
13509   return FALSE;
13510 }
13511
13512 int 
13513 ParseOption(Option *opt, ChessProgramState *cps)
13514 // [HGM] options: process the string that defines an engine option, and determine
13515 // name, type, default value, and allowed value range
13516 {
13517         char *p, *q, buf[MSG_SIZ];
13518         int n, min = (-1)<<31, max = 1<<31, def;
13519
13520         if(p = strstr(opt->name, " -spin ")) {
13521             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13522             if(max < min) max = min; // enforce consistency
13523             if(def < min) def = min;
13524             if(def > max) def = max;
13525             opt->value = def;
13526             opt->min = min;
13527             opt->max = max;
13528             opt->type = Spin;
13529         } else if((p = strstr(opt->name, " -slider "))) {
13530             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13531             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13532             if(max < min) max = min; // enforce consistency
13533             if(def < min) def = min;
13534             if(def > max) def = max;
13535             opt->value = def;
13536             opt->min = min;
13537             opt->max = max;
13538             opt->type = Spin; // Slider;
13539         } else if((p = strstr(opt->name, " -string "))) {
13540             opt->textValue = p+9;
13541             opt->type = TextBox;
13542         } else if((p = strstr(opt->name, " -file "))) {
13543             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13544             opt->textValue = p+7;
13545             opt->type = TextBox; // FileName;
13546         } else if((p = strstr(opt->name, " -path "))) {
13547             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13548             opt->textValue = p+7;
13549             opt->type = TextBox; // PathName;
13550         } else if(p = strstr(opt->name, " -check ")) {
13551             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13552             opt->value = (def != 0);
13553             opt->type = CheckBox;
13554         } else if(p = strstr(opt->name, " -combo ")) {
13555             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13556             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13557             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13558             opt->value = n = 0;
13559             while(q = StrStr(q, " /// ")) {
13560                 n++; *q = 0;    // count choices, and null-terminate each of them
13561                 q += 5;
13562                 if(*q == '*') { // remember default, which is marked with * prefix
13563                     q++;
13564                     opt->value = n;
13565                 }
13566                 cps->comboList[cps->comboCnt++] = q;
13567             }
13568             cps->comboList[cps->comboCnt++] = NULL;
13569             opt->max = n + 1;
13570             opt->type = ComboBox;
13571         } else if(p = strstr(opt->name, " -button")) {
13572             opt->type = Button;
13573         } else if(p = strstr(opt->name, " -save")) {
13574             opt->type = SaveButton;
13575         } else return FALSE;
13576         *p = 0; // terminate option name
13577         // now look if the command-line options define a setting for this engine option.
13578         if(cps->optionSettings && cps->optionSettings[0])
13579             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13580         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13581                 sprintf(buf, "option %s", p);
13582                 if(p = strstr(buf, ",")) *p = 0;
13583                 strcat(buf, "\n");
13584                 SendToProgram(buf, cps);
13585         }
13586         return TRUE;
13587 }
13588
13589 void
13590 FeatureDone(cps, val)
13591      ChessProgramState* cps;
13592      int val;
13593 {
13594   DelayedEventCallback cb = GetDelayedEvent();
13595   if ((cb == InitBackEnd3 && cps == &first) ||
13596       (cb == TwoMachinesEventIfReady && cps == &second)) {
13597     CancelDelayedEvent();
13598     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13599   }
13600   cps->initDone = val;
13601 }
13602
13603 /* Parse feature command from engine */
13604 void
13605 ParseFeatures(args, cps)
13606      char* args;
13607      ChessProgramState *cps;  
13608 {
13609   char *p = args;
13610   char *q;
13611   int val;
13612   char buf[MSG_SIZ];
13613
13614   for (;;) {
13615     while (*p == ' ') p++;
13616     if (*p == NULLCHAR) return;
13617
13618     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13619     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13620     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13621     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13622     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13623     if (BoolFeature(&p, "reuse", &val, cps)) {
13624       /* Engine can disable reuse, but can't enable it if user said no */
13625       if (!val) cps->reuse = FALSE;
13626       continue;
13627     }
13628     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13629     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13630       if (gameMode == TwoMachinesPlay) {
13631         DisplayTwoMachinesTitle();
13632       } else {
13633         DisplayTitle("");
13634       }
13635       continue;
13636     }
13637     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13638     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13639     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13640     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13641     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13642     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13643     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13644     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13645     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13646     if (IntFeature(&p, "done", &val, cps)) {
13647       FeatureDone(cps, val);
13648       continue;
13649     }
13650     /* Added by Tord: */
13651     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13652     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13653     /* End of additions by Tord */
13654
13655     /* [HGM] added features: */
13656     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13657     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13658     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13659     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13660     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13661     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13662     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13663         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13664             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13665             SendToProgram(buf, cps);
13666             continue;
13667         }
13668         if(cps->nrOptions >= MAX_OPTIONS) {
13669             cps->nrOptions--;
13670             sprintf(buf, "%s engine has too many options\n", cps->which);
13671             DisplayError(buf, 0);
13672         }
13673         continue;
13674     }
13675     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13676     /* End of additions by HGM */
13677
13678     /* unknown feature: complain and skip */
13679     q = p;
13680     while (*q && *q != '=') q++;
13681     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13682     SendToProgram(buf, cps);
13683     p = q;
13684     if (*p == '=') {
13685       p++;
13686       if (*p == '\"') {
13687         p++;
13688         while (*p && *p != '\"') p++;
13689         if (*p == '\"') p++;
13690       } else {
13691         while (*p && *p != ' ') p++;
13692       }
13693     }
13694   }
13695
13696 }
13697
13698 void
13699 PeriodicUpdatesEvent(newState)
13700      int newState;
13701 {
13702     if (newState == appData.periodicUpdates)
13703       return;
13704
13705     appData.periodicUpdates=newState;
13706
13707     /* Display type changes, so update it now */
13708 //    DisplayAnalysis();
13709
13710     /* Get the ball rolling again... */
13711     if (newState) {
13712         AnalysisPeriodicEvent(1);
13713         StartAnalysisClock();
13714     }
13715 }
13716
13717 void
13718 PonderNextMoveEvent(newState)
13719      int newState;
13720 {
13721     if (newState == appData.ponderNextMove) return;
13722     if (gameMode == EditPosition) EditPositionDone(TRUE);
13723     if (newState) {
13724         SendToProgram("hard\n", &first);
13725         if (gameMode == TwoMachinesPlay) {
13726             SendToProgram("hard\n", &second);
13727         }
13728     } else {
13729         SendToProgram("easy\n", &first);
13730         thinkOutput[0] = NULLCHAR;
13731         if (gameMode == TwoMachinesPlay) {
13732             SendToProgram("easy\n", &second);
13733         }
13734     }
13735     appData.ponderNextMove = newState;
13736 }
13737
13738 void
13739 NewSettingEvent(option, command, value)
13740      char *command;
13741      int option, value;
13742 {
13743     char buf[MSG_SIZ];
13744
13745     if (gameMode == EditPosition) EditPositionDone(TRUE);
13746     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13747     SendToProgram(buf, &first);
13748     if (gameMode == TwoMachinesPlay) {
13749         SendToProgram(buf, &second);
13750     }
13751 }
13752
13753 void
13754 ShowThinkingEvent()
13755 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13756 {
13757     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13758     int newState = appData.showThinking
13759         // [HGM] thinking: other features now need thinking output as well
13760         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13761     
13762     if (oldState == newState) return;
13763     oldState = newState;
13764     if (gameMode == EditPosition) EditPositionDone(TRUE);
13765     if (oldState) {
13766         SendToProgram("post\n", &first);
13767         if (gameMode == TwoMachinesPlay) {
13768             SendToProgram("post\n", &second);
13769         }
13770     } else {
13771         SendToProgram("nopost\n", &first);
13772         thinkOutput[0] = NULLCHAR;
13773         if (gameMode == TwoMachinesPlay) {
13774             SendToProgram("nopost\n", &second);
13775         }
13776     }
13777 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13778 }
13779
13780 void
13781 AskQuestionEvent(title, question, replyPrefix, which)
13782      char *title; char *question; char *replyPrefix; char *which;
13783 {
13784   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13785   if (pr == NoProc) return;
13786   AskQuestion(title, question, replyPrefix, pr);
13787 }
13788
13789 void
13790 DisplayMove(moveNumber)
13791      int moveNumber;
13792 {
13793     char message[MSG_SIZ];
13794     char res[MSG_SIZ];
13795     char cpThinkOutput[MSG_SIZ];
13796
13797     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13798     
13799     if (moveNumber == forwardMostMove - 1 || 
13800         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13801
13802         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13803
13804         if (strchr(cpThinkOutput, '\n')) {
13805             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13806         }
13807     } else {
13808         *cpThinkOutput = NULLCHAR;
13809     }
13810
13811     /* [AS] Hide thinking from human user */
13812     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13813         *cpThinkOutput = NULLCHAR;
13814         if( thinkOutput[0] != NULLCHAR ) {
13815             int i;
13816
13817             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13818                 cpThinkOutput[i] = '.';
13819             }
13820             cpThinkOutput[i] = NULLCHAR;
13821             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13822         }
13823     }
13824
13825     if (moveNumber == forwardMostMove - 1 &&
13826         gameInfo.resultDetails != NULL) {
13827         if (gameInfo.resultDetails[0] == NULLCHAR) {
13828             sprintf(res, " %s", PGNResult(gameInfo.result));
13829         } else {
13830             sprintf(res, " {%s} %s",
13831                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13832         }
13833     } else {
13834         res[0] = NULLCHAR;
13835     }
13836
13837     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13838         DisplayMessage(res, cpThinkOutput);
13839     } else {
13840         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13841                 WhiteOnMove(moveNumber) ? " " : ".. ",
13842                 parseList[moveNumber], res);
13843         DisplayMessage(message, cpThinkOutput);
13844     }
13845 }
13846
13847 void
13848 DisplayComment(moveNumber, text)
13849      int moveNumber;
13850      char *text;
13851 {
13852     char title[MSG_SIZ];
13853     char buf[8000]; // comment can be long!
13854     int score, depth;
13855     
13856     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13857       strcpy(title, "Comment");
13858     } else {
13859       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13860               WhiteOnMove(moveNumber) ? " " : ".. ",
13861               parseList[moveNumber]);
13862     }
13863     // [HGM] PV info: display PV info together with (or as) comment
13864     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13865       if(text == NULL) text = "";                                           
13866       score = pvInfoList[moveNumber].score;
13867       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13868               depth, (pvInfoList[moveNumber].time+50)/100, text);
13869       text = buf;
13870     }
13871     if (text != NULL && (appData.autoDisplayComment || commentUp))
13872         CommentPopUp(title, text);
13873 }
13874
13875 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13876  * might be busy thinking or pondering.  It can be omitted if your
13877  * gnuchess is configured to stop thinking immediately on any user
13878  * input.  However, that gnuchess feature depends on the FIONREAD
13879  * ioctl, which does not work properly on some flavors of Unix.
13880  */
13881 void
13882 Attention(cps)
13883      ChessProgramState *cps;
13884 {
13885 #if ATTENTION
13886     if (!cps->useSigint) return;
13887     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13888     switch (gameMode) {
13889       case MachinePlaysWhite:
13890       case MachinePlaysBlack:
13891       case TwoMachinesPlay:
13892       case IcsPlayingWhite:
13893       case IcsPlayingBlack:
13894       case AnalyzeMode:
13895       case AnalyzeFile:
13896         /* Skip if we know it isn't thinking */
13897         if (!cps->maybeThinking) return;
13898         if (appData.debugMode)
13899           fprintf(debugFP, "Interrupting %s\n", cps->which);
13900         InterruptChildProcess(cps->pr);
13901         cps->maybeThinking = FALSE;
13902         break;
13903       default:
13904         break;
13905     }
13906 #endif /*ATTENTION*/
13907 }
13908
13909 int
13910 CheckFlags()
13911 {
13912     if (whiteTimeRemaining <= 0) {
13913         if (!whiteFlag) {
13914             whiteFlag = TRUE;
13915             if (appData.icsActive) {
13916                 if (appData.autoCallFlag &&
13917                     gameMode == IcsPlayingBlack && !blackFlag) {
13918                   SendToICS(ics_prefix);
13919                   SendToICS("flag\n");
13920                 }
13921             } else {
13922                 if (blackFlag) {
13923                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13924                 } else {
13925                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13926                     if (appData.autoCallFlag) {
13927                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13928                         return TRUE;
13929                     }
13930                 }
13931             }
13932         }
13933     }
13934     if (blackTimeRemaining <= 0) {
13935         if (!blackFlag) {
13936             blackFlag = TRUE;
13937             if (appData.icsActive) {
13938                 if (appData.autoCallFlag &&
13939                     gameMode == IcsPlayingWhite && !whiteFlag) {
13940                   SendToICS(ics_prefix);
13941                   SendToICS("flag\n");
13942                 }
13943             } else {
13944                 if (whiteFlag) {
13945                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13946                 } else {
13947                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13948                     if (appData.autoCallFlag) {
13949                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13950                         return TRUE;
13951                     }
13952                 }
13953             }
13954         }
13955     }
13956     return FALSE;
13957 }
13958
13959 void
13960 CheckTimeControl()
13961 {
13962     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13963         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13964
13965     /*
13966      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13967      */
13968     if ( !WhiteOnMove(forwardMostMove) )
13969         /* White made time control */
13970         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13971         /* [HGM] time odds: correct new time quota for time odds! */
13972                                             / WhitePlayer()->timeOdds;
13973       else
13974         /* Black made time control */
13975         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13976                                             / WhitePlayer()->other->timeOdds;
13977 }
13978
13979 void
13980 DisplayBothClocks()
13981 {
13982     int wom = gameMode == EditPosition ?
13983       !blackPlaysFirst : WhiteOnMove(currentMove);
13984     DisplayWhiteClock(whiteTimeRemaining, wom);
13985     DisplayBlackClock(blackTimeRemaining, !wom);
13986 }
13987
13988
13989 /* Timekeeping seems to be a portability nightmare.  I think everyone
13990    has ftime(), but I'm really not sure, so I'm including some ifdefs
13991    to use other calls if you don't.  Clocks will be less accurate if
13992    you have neither ftime nor gettimeofday.
13993 */
13994
13995 /* VS 2008 requires the #include outside of the function */
13996 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13997 #include <sys/timeb.h>
13998 #endif
13999
14000 /* Get the current time as a TimeMark */
14001 void
14002 GetTimeMark(tm)
14003      TimeMark *tm;
14004 {
14005 #if HAVE_GETTIMEOFDAY
14006
14007     struct timeval timeVal;
14008     struct timezone timeZone;
14009
14010     gettimeofday(&timeVal, &timeZone);
14011     tm->sec = (long) timeVal.tv_sec; 
14012     tm->ms = (int) (timeVal.tv_usec / 1000L);
14013
14014 #else /*!HAVE_GETTIMEOFDAY*/
14015 #if HAVE_FTIME
14016
14017 // include <sys/timeb.h> / moved to just above start of function
14018     struct timeb timeB;
14019
14020     ftime(&timeB);
14021     tm->sec = (long) timeB.time;
14022     tm->ms = (int) timeB.millitm;
14023
14024 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14025     tm->sec = (long) time(NULL);
14026     tm->ms = 0;
14027 #endif
14028 #endif
14029 }
14030
14031 /* Return the difference in milliseconds between two
14032    time marks.  We assume the difference will fit in a long!
14033 */
14034 long
14035 SubtractTimeMarks(tm2, tm1)
14036      TimeMark *tm2, *tm1;
14037 {
14038     return 1000L*(tm2->sec - tm1->sec) +
14039            (long) (tm2->ms - tm1->ms);
14040 }
14041
14042
14043 /*
14044  * Code to manage the game clocks.
14045  *
14046  * In tournament play, black starts the clock and then white makes a move.
14047  * We give the human user a slight advantage if he is playing white---the
14048  * clocks don't run until he makes his first move, so it takes zero time.
14049  * Also, we don't account for network lag, so we could get out of sync
14050  * with GNU Chess's clock -- but then, referees are always right.  
14051  */
14052
14053 static TimeMark tickStartTM;
14054 static long intendedTickLength;
14055
14056 long
14057 NextTickLength(timeRemaining)
14058      long timeRemaining;
14059 {
14060     long nominalTickLength, nextTickLength;
14061
14062     if (timeRemaining > 0L && timeRemaining <= 10000L)
14063       nominalTickLength = 100L;
14064     else
14065       nominalTickLength = 1000L;
14066     nextTickLength = timeRemaining % nominalTickLength;
14067     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14068
14069     return nextTickLength;
14070 }
14071
14072 /* Adjust clock one minute up or down */
14073 void
14074 AdjustClock(Boolean which, int dir)
14075 {
14076     if(which) blackTimeRemaining += 60000*dir;
14077     else      whiteTimeRemaining += 60000*dir;
14078     DisplayBothClocks();
14079 }
14080
14081 /* Stop clocks and reset to a fresh time control */
14082 void
14083 ResetClocks() 
14084 {
14085     (void) StopClockTimer();
14086     if (appData.icsActive) {
14087         whiteTimeRemaining = blackTimeRemaining = 0;
14088     } else if (searchTime) {
14089         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14090         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14091     } else { /* [HGM] correct new time quote for time odds */
14092         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14093         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14094     }
14095     if (whiteFlag || blackFlag) {
14096         DisplayTitle("");
14097         whiteFlag = blackFlag = FALSE;
14098     }
14099     DisplayBothClocks();
14100 }
14101
14102 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14103
14104 /* Decrement running clock by amount of time that has passed */
14105 void
14106 DecrementClocks()
14107 {
14108     long timeRemaining;
14109     long lastTickLength, fudge;
14110     TimeMark now;
14111
14112     if (!appData.clockMode) return;
14113     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14114         
14115     GetTimeMark(&now);
14116
14117     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14118
14119     /* Fudge if we woke up a little too soon */
14120     fudge = intendedTickLength - lastTickLength;
14121     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14122
14123     if (WhiteOnMove(forwardMostMove)) {
14124         if(whiteNPS >= 0) lastTickLength = 0;
14125         timeRemaining = whiteTimeRemaining -= lastTickLength;
14126         DisplayWhiteClock(whiteTimeRemaining - fudge,
14127                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14128     } else {
14129         if(blackNPS >= 0) lastTickLength = 0;
14130         timeRemaining = blackTimeRemaining -= lastTickLength;
14131         DisplayBlackClock(blackTimeRemaining - fudge,
14132                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14133     }
14134
14135     if (CheckFlags()) return;
14136         
14137     tickStartTM = now;
14138     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14139     StartClockTimer(intendedTickLength);
14140
14141     /* if the time remaining has fallen below the alarm threshold, sound the
14142      * alarm. if the alarm has sounded and (due to a takeback or time control
14143      * with increment) the time remaining has increased to a level above the
14144      * threshold, reset the alarm so it can sound again. 
14145      */
14146     
14147     if (appData.icsActive && appData.icsAlarm) {
14148
14149         /* make sure we are dealing with the user's clock */
14150         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14151                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14152            )) return;
14153
14154         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14155             alarmSounded = FALSE;
14156         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14157             PlayAlarmSound();
14158             alarmSounded = TRUE;
14159         }
14160     }
14161 }
14162
14163
14164 /* A player has just moved, so stop the previously running
14165    clock and (if in clock mode) start the other one.
14166    We redisplay both clocks in case we're in ICS mode, because
14167    ICS gives us an update to both clocks after every move.
14168    Note that this routine is called *after* forwardMostMove
14169    is updated, so the last fractional tick must be subtracted
14170    from the color that is *not* on move now.
14171 */
14172 void
14173 SwitchClocks(int newMoveNr)
14174 {
14175     long lastTickLength;
14176     TimeMark now;
14177     int flagged = FALSE;
14178
14179     GetTimeMark(&now);
14180
14181     if (StopClockTimer() && appData.clockMode) {
14182         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14183         if (!WhiteOnMove(forwardMostMove)) {
14184             if(blackNPS >= 0) lastTickLength = 0;
14185             blackTimeRemaining -= lastTickLength;
14186            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14187 //         if(pvInfoList[forwardMostMove-1].time == -1)
14188                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14189                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14190         } else {
14191            if(whiteNPS >= 0) lastTickLength = 0;
14192            whiteTimeRemaining -= lastTickLength;
14193            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14194 //         if(pvInfoList[forwardMostMove-1].time == -1)
14195                  pvInfoList[forwardMostMove-1].time = 
14196                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14197         }
14198         flagged = CheckFlags();
14199     }
14200     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14201     CheckTimeControl();
14202
14203     if (flagged || !appData.clockMode) return;
14204
14205     switch (gameMode) {
14206       case MachinePlaysBlack:
14207       case MachinePlaysWhite:
14208       case BeginningOfGame:
14209         if (pausing) return;
14210         break;
14211
14212       case EditGame:
14213       case PlayFromGameFile:
14214       case IcsExamining:
14215         return;
14216
14217       default:
14218         break;
14219     }
14220
14221     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14222         if(WhiteOnMove(forwardMostMove))
14223              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14224         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14225     }
14226
14227     tickStartTM = now;
14228     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14229       whiteTimeRemaining : blackTimeRemaining);
14230     StartClockTimer(intendedTickLength);
14231 }
14232         
14233
14234 /* Stop both clocks */
14235 void
14236 StopClocks()
14237 {       
14238     long lastTickLength;
14239     TimeMark now;
14240
14241     if (!StopClockTimer()) return;
14242     if (!appData.clockMode) return;
14243
14244     GetTimeMark(&now);
14245
14246     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14247     if (WhiteOnMove(forwardMostMove)) {
14248         if(whiteNPS >= 0) lastTickLength = 0;
14249         whiteTimeRemaining -= lastTickLength;
14250         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14251     } else {
14252         if(blackNPS >= 0) lastTickLength = 0;
14253         blackTimeRemaining -= lastTickLength;
14254         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14255     }
14256     CheckFlags();
14257 }
14258         
14259 /* Start clock of player on move.  Time may have been reset, so
14260    if clock is already running, stop and restart it. */
14261 void
14262 StartClocks()
14263 {
14264     (void) StopClockTimer(); /* in case it was running already */
14265     DisplayBothClocks();
14266     if (CheckFlags()) return;
14267
14268     if (!appData.clockMode) return;
14269     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14270
14271     GetTimeMark(&tickStartTM);
14272     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14273       whiteTimeRemaining : blackTimeRemaining);
14274
14275    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14276     whiteNPS = blackNPS = -1; 
14277     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14278        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14279         whiteNPS = first.nps;
14280     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14281        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14282         blackNPS = first.nps;
14283     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14284         whiteNPS = second.nps;
14285     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14286         blackNPS = second.nps;
14287     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14288
14289     StartClockTimer(intendedTickLength);
14290 }
14291
14292 char *
14293 TimeString(ms)
14294      long ms;
14295 {
14296     long second, minute, hour, day;
14297     char *sign = "";
14298     static char buf[32];
14299     
14300     if (ms > 0 && ms <= 9900) {
14301       /* convert milliseconds to tenths, rounding up */
14302       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14303
14304       sprintf(buf, " %03.1f ", tenths/10.0);
14305       return buf;
14306     }
14307
14308     /* convert milliseconds to seconds, rounding up */
14309     /* use floating point to avoid strangeness of integer division
14310        with negative dividends on many machines */
14311     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14312
14313     if (second < 0) {
14314         sign = "-";
14315         second = -second;
14316     }
14317     
14318     day = second / (60 * 60 * 24);
14319     second = second % (60 * 60 * 24);
14320     hour = second / (60 * 60);
14321     second = second % (60 * 60);
14322     minute = second / 60;
14323     second = second % 60;
14324     
14325     if (day > 0)
14326       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14327               sign, day, hour, minute, second);
14328     else if (hour > 0)
14329       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14330     else
14331       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14332     
14333     return buf;
14334 }
14335
14336
14337 /*
14338  * This is necessary because some C libraries aren't ANSI C compliant yet.
14339  */
14340 char *
14341 StrStr(string, match)
14342      char *string, *match;
14343 {
14344     int i, length;
14345     
14346     length = strlen(match);
14347     
14348     for (i = strlen(string) - length; i >= 0; i--, string++)
14349       if (!strncmp(match, string, length))
14350         return string;
14351     
14352     return NULL;
14353 }
14354
14355 char *
14356 StrCaseStr(string, match)
14357      char *string, *match;
14358 {
14359     int i, j, length;
14360     
14361     length = strlen(match);
14362     
14363     for (i = strlen(string) - length; i >= 0; i--, string++) {
14364         for (j = 0; j < length; j++) {
14365             if (ToLower(match[j]) != ToLower(string[j]))
14366               break;
14367         }
14368         if (j == length) return string;
14369     }
14370
14371     return NULL;
14372 }
14373
14374 #ifndef _amigados
14375 int
14376 StrCaseCmp(s1, s2)
14377      char *s1, *s2;
14378 {
14379     char c1, c2;
14380     
14381     for (;;) {
14382         c1 = ToLower(*s1++);
14383         c2 = ToLower(*s2++);
14384         if (c1 > c2) return 1;
14385         if (c1 < c2) return -1;
14386         if (c1 == NULLCHAR) return 0;
14387     }
14388 }
14389
14390
14391 int
14392 ToLower(c)
14393      int c;
14394 {
14395     return isupper(c) ? tolower(c) : c;
14396 }
14397
14398
14399 int
14400 ToUpper(c)
14401      int c;
14402 {
14403     return islower(c) ? toupper(c) : c;
14404 }
14405 #endif /* !_amigados    */
14406
14407 char *
14408 StrSave(s)
14409      char *s;
14410 {
14411     char *ret;
14412
14413     if ((ret = (char *) malloc(strlen(s) + 1))) {
14414         strcpy(ret, s);
14415     }
14416     return ret;
14417 }
14418
14419 char *
14420 StrSavePtr(s, savePtr)
14421      char *s, **savePtr;
14422 {
14423     if (*savePtr) {
14424         free(*savePtr);
14425     }
14426     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14427         strcpy(*savePtr, s);
14428     }
14429     return(*savePtr);
14430 }
14431
14432 char *
14433 PGNDate()
14434 {
14435     time_t clock;
14436     struct tm *tm;
14437     char buf[MSG_SIZ];
14438
14439     clock = time((time_t *)NULL);
14440     tm = localtime(&clock);
14441     sprintf(buf, "%04d.%02d.%02d",
14442             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14443     return StrSave(buf);
14444 }
14445
14446
14447 char *
14448 PositionToFEN(move, overrideCastling)
14449      int move;
14450      char *overrideCastling;
14451 {
14452     int i, j, fromX, fromY, toX, toY;
14453     int whiteToPlay;
14454     char buf[128];
14455     char *p, *q;
14456     int emptycount;
14457     ChessSquare piece;
14458
14459     whiteToPlay = (gameMode == EditPosition) ?
14460       !blackPlaysFirst : (move % 2 == 0);
14461     p = buf;
14462
14463     /* Piece placement data */
14464     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14465         emptycount = 0;
14466         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14467             if (boards[move][i][j] == EmptySquare) {
14468                 emptycount++;
14469             } else { ChessSquare piece = boards[move][i][j];
14470                 if (emptycount > 0) {
14471                     if(emptycount<10) /* [HGM] can be >= 10 */
14472                         *p++ = '0' + emptycount;
14473                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14474                     emptycount = 0;
14475                 }
14476                 if(PieceToChar(piece) == '+') {
14477                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14478                     *p++ = '+';
14479                     piece = (ChessSquare)(DEMOTED piece);
14480                 } 
14481                 *p++ = PieceToChar(piece);
14482                 if(p[-1] == '~') {
14483                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14484                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14485                     *p++ = '~';
14486                 }
14487             }
14488         }
14489         if (emptycount > 0) {
14490             if(emptycount<10) /* [HGM] can be >= 10 */
14491                 *p++ = '0' + emptycount;
14492             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14493             emptycount = 0;
14494         }
14495         *p++ = '/';
14496     }
14497     *(p - 1) = ' ';
14498
14499     /* [HGM] print Crazyhouse or Shogi holdings */
14500     if( gameInfo.holdingsWidth ) {
14501         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14502         q = p;
14503         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14504             piece = boards[move][i][BOARD_WIDTH-1];
14505             if( piece != EmptySquare )
14506               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14507                   *p++ = PieceToChar(piece);
14508         }
14509         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14510             piece = boards[move][BOARD_HEIGHT-i-1][0];
14511             if( piece != EmptySquare )
14512               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14513                   *p++ = PieceToChar(piece);
14514         }
14515
14516         if( q == p ) *p++ = '-';
14517         *p++ = ']';
14518         *p++ = ' ';
14519     }
14520
14521     /* Active color */
14522     *p++ = whiteToPlay ? 'w' : 'b';
14523     *p++ = ' ';
14524
14525   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14526     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14527   } else {
14528   if(nrCastlingRights) {
14529      q = p;
14530      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14531        /* [HGM] write directly from rights */
14532            if(boards[move][CASTLING][2] != NoRights &&
14533               boards[move][CASTLING][0] != NoRights   )
14534                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14535            if(boards[move][CASTLING][2] != NoRights &&
14536               boards[move][CASTLING][1] != NoRights   )
14537                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14538            if(boards[move][CASTLING][5] != NoRights &&
14539               boards[move][CASTLING][3] != NoRights   )
14540                 *p++ = boards[move][CASTLING][3] + AAA;
14541            if(boards[move][CASTLING][5] != NoRights &&
14542               boards[move][CASTLING][4] != NoRights   )
14543                 *p++ = boards[move][CASTLING][4] + AAA;
14544      } else {
14545
14546         /* [HGM] write true castling rights */
14547         if( nrCastlingRights == 6 ) {
14548             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14549                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14550             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14551                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14552             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14553                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14554             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14555                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14556         }
14557      }
14558      if (q == p) *p++ = '-'; /* No castling rights */
14559      *p++ = ' ';
14560   }
14561
14562   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14563      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14564     /* En passant target square */
14565     if (move > backwardMostMove) {
14566         fromX = moveList[move - 1][0] - AAA;
14567         fromY = moveList[move - 1][1] - ONE;
14568         toX = moveList[move - 1][2] - AAA;
14569         toY = moveList[move - 1][3] - ONE;
14570         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14571             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14572             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14573             fromX == toX) {
14574             /* 2-square pawn move just happened */
14575             *p++ = toX + AAA;
14576             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14577         } else {
14578             *p++ = '-';
14579         }
14580     } else if(move == backwardMostMove) {
14581         // [HGM] perhaps we should always do it like this, and forget the above?
14582         if((signed char)boards[move][EP_STATUS] >= 0) {
14583             *p++ = boards[move][EP_STATUS] + AAA;
14584             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14585         } else {
14586             *p++ = '-';
14587         }
14588     } else {
14589         *p++ = '-';
14590     }
14591     *p++ = ' ';
14592   }
14593   }
14594
14595     /* [HGM] find reversible plies */
14596     {   int i = 0, j=move;
14597
14598         if (appData.debugMode) { int k;
14599             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14600             for(k=backwardMostMove; k<=forwardMostMove; k++)
14601                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14602
14603         }
14604
14605         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14606         if( j == backwardMostMove ) i += initialRulePlies;
14607         sprintf(p, "%d ", i);
14608         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14609     }
14610     /* Fullmove number */
14611     sprintf(p, "%d", (move / 2) + 1);
14612     
14613     return StrSave(buf);
14614 }
14615
14616 Boolean
14617 ParseFEN(board, blackPlaysFirst, fen)
14618     Board board;
14619      int *blackPlaysFirst;
14620      char *fen;
14621 {
14622     int i, j;
14623     char *p;
14624     int emptycount;
14625     ChessSquare piece;
14626
14627     p = fen;
14628
14629     /* [HGM] by default clear Crazyhouse holdings, if present */
14630     if(gameInfo.holdingsWidth) {
14631        for(i=0; i<BOARD_HEIGHT; i++) {
14632            board[i][0]             = EmptySquare; /* black holdings */
14633            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14634            board[i][1]             = (ChessSquare) 0; /* black counts */
14635            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14636        }
14637     }
14638
14639     /* Piece placement data */
14640     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14641         j = 0;
14642         for (;;) {
14643             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14644                 if (*p == '/') p++;
14645                 emptycount = gameInfo.boardWidth - j;
14646                 while (emptycount--)
14647                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14648                 break;
14649 #if(BOARD_FILES >= 10)
14650             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14651                 p++; emptycount=10;
14652                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14653                 while (emptycount--)
14654                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14655 #endif
14656             } else if (isdigit(*p)) {
14657                 emptycount = *p++ - '0';
14658                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14659                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14660                 while (emptycount--)
14661                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14662             } else if (*p == '+' || isalpha(*p)) {
14663                 if (j >= gameInfo.boardWidth) return FALSE;
14664                 if(*p=='+') {
14665                     piece = CharToPiece(*++p);
14666                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14667                     piece = (ChessSquare) (PROMOTED piece ); p++;
14668                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14669                 } else piece = CharToPiece(*p++);
14670
14671                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14672                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14673                     piece = (ChessSquare) (PROMOTED piece);
14674                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14675                     p++;
14676                 }
14677                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14678             } else {
14679                 return FALSE;
14680             }
14681         }
14682     }
14683     while (*p == '/' || *p == ' ') p++;
14684
14685     /* [HGM] look for Crazyhouse holdings here */
14686     while(*p==' ') p++;
14687     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14688         if(*p == '[') p++;
14689         if(*p == '-' ) *p++; /* empty holdings */ else {
14690             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14691             /* if we would allow FEN reading to set board size, we would   */
14692             /* have to add holdings and shift the board read so far here   */
14693             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14694                 *p++;
14695                 if((int) piece >= (int) BlackPawn ) {
14696                     i = (int)piece - (int)BlackPawn;
14697                     i = PieceToNumber((ChessSquare)i);
14698                     if( i >= gameInfo.holdingsSize ) return FALSE;
14699                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14700                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14701                 } else {
14702                     i = (int)piece - (int)WhitePawn;
14703                     i = PieceToNumber((ChessSquare)i);
14704                     if( i >= gameInfo.holdingsSize ) return FALSE;
14705                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14706                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14707                 }
14708             }
14709         }
14710         if(*p == ']') *p++;
14711     }
14712
14713     while(*p == ' ') p++;
14714
14715     /* Active color */
14716     switch (*p++) {
14717       case 'w':
14718         *blackPlaysFirst = FALSE;
14719         break;
14720       case 'b': 
14721         *blackPlaysFirst = TRUE;
14722         break;
14723       default:
14724         return FALSE;
14725     }
14726
14727     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14728     /* return the extra info in global variiables             */
14729
14730     /* set defaults in case FEN is incomplete */
14731     board[EP_STATUS] = EP_UNKNOWN;
14732     for(i=0; i<nrCastlingRights; i++ ) {
14733         board[CASTLING][i] =
14734             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14735     }   /* assume possible unless obviously impossible */
14736     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14737     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14738     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14739                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14740     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14741     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14742     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14743                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14744     FENrulePlies = 0;
14745
14746     while(*p==' ') p++;
14747     if(nrCastlingRights) {
14748       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14749           /* castling indicator present, so default becomes no castlings */
14750           for(i=0; i<nrCastlingRights; i++ ) {
14751                  board[CASTLING][i] = NoRights;
14752           }
14753       }
14754       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14755              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14756              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14757              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14758         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14759
14760         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14761             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14762             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14763         }
14764         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14765             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14766         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14767                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14768         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14769                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14770         switch(c) {
14771           case'K':
14772               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14773               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14774               board[CASTLING][2] = whiteKingFile;
14775               break;
14776           case'Q':
14777               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14778               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14779               board[CASTLING][2] = whiteKingFile;
14780               break;
14781           case'k':
14782               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14783               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14784               board[CASTLING][5] = blackKingFile;
14785               break;
14786           case'q':
14787               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14788               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14789               board[CASTLING][5] = blackKingFile;
14790           case '-':
14791               break;
14792           default: /* FRC castlings */
14793               if(c >= 'a') { /* black rights */
14794                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14795                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14796                   if(i == BOARD_RGHT) break;
14797                   board[CASTLING][5] = i;
14798                   c -= AAA;
14799                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14800                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14801                   if(c > i)
14802                       board[CASTLING][3] = c;
14803                   else
14804                       board[CASTLING][4] = c;
14805               } else { /* white rights */
14806                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14807                     if(board[0][i] == WhiteKing) break;
14808                   if(i == BOARD_RGHT) break;
14809                   board[CASTLING][2] = i;
14810                   c -= AAA - 'a' + 'A';
14811                   if(board[0][c] >= WhiteKing) break;
14812                   if(c > i)
14813                       board[CASTLING][0] = c;
14814                   else
14815                       board[CASTLING][1] = c;
14816               }
14817         }
14818       }
14819       for(i=0; i<nrCastlingRights; i++)
14820         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14821     if (appData.debugMode) {
14822         fprintf(debugFP, "FEN castling rights:");
14823         for(i=0; i<nrCastlingRights; i++)
14824         fprintf(debugFP, " %d", board[CASTLING][i]);
14825         fprintf(debugFP, "\n");
14826     }
14827
14828       while(*p==' ') p++;
14829     }
14830
14831     /* read e.p. field in games that know e.p. capture */
14832     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14833        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14834       if(*p=='-') {
14835         p++; board[EP_STATUS] = EP_NONE;
14836       } else {
14837          char c = *p++ - AAA;
14838
14839          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14840          if(*p >= '0' && *p <='9') *p++;
14841          board[EP_STATUS] = c;
14842       }
14843     }
14844
14845
14846     if(sscanf(p, "%d", &i) == 1) {
14847         FENrulePlies = i; /* 50-move ply counter */
14848         /* (The move number is still ignored)    */
14849     }
14850
14851     return TRUE;
14852 }
14853       
14854 void
14855 EditPositionPasteFEN(char *fen)
14856 {
14857   if (fen != NULL) {
14858     Board initial_position;
14859
14860     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14861       DisplayError(_("Bad FEN position in clipboard"), 0);
14862       return ;
14863     } else {
14864       int savedBlackPlaysFirst = blackPlaysFirst;
14865       EditPositionEvent();
14866       blackPlaysFirst = savedBlackPlaysFirst;
14867       CopyBoard(boards[0], initial_position);
14868       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14869       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14870       DisplayBothClocks();
14871       DrawPosition(FALSE, boards[currentMove]);
14872     }
14873   }
14874 }
14875
14876 static char cseq[12] = "\\   ";
14877
14878 Boolean set_cont_sequence(char *new_seq)
14879 {
14880     int len;
14881     Boolean ret;
14882
14883     // handle bad attempts to set the sequence
14884         if (!new_seq)
14885                 return 0; // acceptable error - no debug
14886
14887     len = strlen(new_seq);
14888     ret = (len > 0) && (len < sizeof(cseq));
14889     if (ret)
14890         strcpy(cseq, new_seq);
14891     else if (appData.debugMode)
14892         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14893     return ret;
14894 }
14895
14896 /*
14897     reformat a source message so words don't cross the width boundary.  internal
14898     newlines are not removed.  returns the wrapped size (no null character unless
14899     included in source message).  If dest is NULL, only calculate the size required
14900     for the dest buffer.  lp argument indicats line position upon entry, and it's
14901     passed back upon exit.
14902 */
14903 int wrap(char *dest, char *src, int count, int width, int *lp)
14904 {
14905     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14906
14907     cseq_len = strlen(cseq);
14908     old_line = line = *lp;
14909     ansi = len = clen = 0;
14910
14911     for (i=0; i < count; i++)
14912     {
14913         if (src[i] == '\033')
14914             ansi = 1;
14915
14916         // if we hit the width, back up
14917         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14918         {
14919             // store i & len in case the word is too long
14920             old_i = i, old_len = len;
14921
14922             // find the end of the last word
14923             while (i && src[i] != ' ' && src[i] != '\n')
14924             {
14925                 i--;
14926                 len--;
14927             }
14928
14929             // word too long?  restore i & len before splitting it
14930             if ((old_i-i+clen) >= width)
14931             {
14932                 i = old_i;
14933                 len = old_len;
14934             }
14935
14936             // extra space?
14937             if (i && src[i-1] == ' ')
14938                 len--;
14939
14940             if (src[i] != ' ' && src[i] != '\n')
14941             {
14942                 i--;
14943                 if (len)
14944                     len--;
14945             }
14946
14947             // now append the newline and continuation sequence
14948             if (dest)
14949                 dest[len] = '\n';
14950             len++;
14951             if (dest)
14952                 strncpy(dest+len, cseq, cseq_len);
14953             len += cseq_len;
14954             line = cseq_len;
14955             clen = cseq_len;
14956             continue;
14957         }
14958
14959         if (dest)
14960             dest[len] = src[i];
14961         len++;
14962         if (!ansi)
14963             line++;
14964         if (src[i] == '\n')
14965             line = 0;
14966         if (src[i] == 'm')
14967             ansi = 0;
14968     }
14969     if (dest && appData.debugMode)
14970     {
14971         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14972             count, width, line, len, *lp);
14973         show_bytes(debugFP, src, count);
14974         fprintf(debugFP, "\ndest: ");
14975         show_bytes(debugFP, dest, len);
14976         fprintf(debugFP, "\n");
14977     }
14978     *lp = dest ? line : old_line;
14979
14980     return len;
14981 }
14982
14983 // [HGM] vari: routines for shelving variations
14984
14985 void 
14986 PushTail(int firstMove, int lastMove)
14987 {
14988         int i, j, nrMoves = lastMove - firstMove;
14989
14990         if(appData.icsActive) { // only in local mode
14991                 forwardMostMove = currentMove; // mimic old ICS behavior
14992                 return;
14993         }
14994         if(storedGames >= MAX_VARIATIONS-1) return;
14995
14996         // push current tail of game on stack
14997         savedResult[storedGames] = gameInfo.result;
14998         savedDetails[storedGames] = gameInfo.resultDetails;
14999         gameInfo.resultDetails = NULL;
15000         savedFirst[storedGames] = firstMove;
15001         savedLast [storedGames] = lastMove;
15002         savedFramePtr[storedGames] = framePtr;
15003         framePtr -= nrMoves; // reserve space for the boards
15004         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15005             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15006             for(j=0; j<MOVE_LEN; j++)
15007                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15008             for(j=0; j<2*MOVE_LEN; j++)
15009                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15010             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15011             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15012             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15013             pvInfoList[firstMove+i-1].depth = 0;
15014             commentList[framePtr+i] = commentList[firstMove+i];
15015             commentList[firstMove+i] = NULL;
15016         }
15017
15018         storedGames++;
15019         forwardMostMove = currentMove; // truncte game so we can start variation
15020         if(storedGames == 1) GreyRevert(FALSE);
15021 }
15022
15023 Boolean
15024 PopTail(Boolean annotate)
15025 {
15026         int i, j, nrMoves;
15027         char buf[8000], moveBuf[20];
15028
15029         if(appData.icsActive) return FALSE; // only in local mode
15030         if(!storedGames) return FALSE; // sanity
15031
15032         storedGames--;
15033         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15034         nrMoves = savedLast[storedGames] - currentMove;
15035         if(annotate) {
15036                 int cnt = 10;
15037                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15038                 else strcpy(buf, "(");
15039                 for(i=currentMove; i<forwardMostMove; i++) {
15040                         if(WhiteOnMove(i))
15041                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15042                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15043                         strcat(buf, moveBuf);
15044                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15045                 }
15046                 strcat(buf, ")");
15047         }
15048         for(i=1; i<nrMoves; i++) { // copy last variation back
15049             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15050             for(j=0; j<MOVE_LEN; j++)
15051                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15052             for(j=0; j<2*MOVE_LEN; j++)
15053                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15054             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15055             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15056             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15057             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15058             commentList[currentMove+i] = commentList[framePtr+i];
15059             commentList[framePtr+i] = NULL;
15060         }
15061         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15062         framePtr = savedFramePtr[storedGames];
15063         gameInfo.result = savedResult[storedGames];
15064         if(gameInfo.resultDetails != NULL) {
15065             free(gameInfo.resultDetails);
15066       }
15067         gameInfo.resultDetails = savedDetails[storedGames];
15068         forwardMostMove = currentMove + nrMoves;
15069         if(storedGames == 0) GreyRevert(TRUE);
15070         return TRUE;
15071 }
15072
15073 void 
15074 CleanupTail()
15075 {       // remove all shelved variations
15076         int i;
15077         for(i=0; i<storedGames; i++) {
15078             if(savedDetails[i])
15079                 free(savedDetails[i]);
15080             savedDetails[i] = NULL;
15081         }
15082         for(i=framePtr; i<MAX_MOVES; i++) {
15083                 if(commentList[i]) free(commentList[i]);
15084                 commentList[i] = NULL;
15085         }
15086         framePtr = MAX_MOVES-1;
15087         storedGames = 0;
15088 }