424463dad2eca1e8477a821e39fb78ca9b85c765
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "* shouts:") ||
2744                                            looking_at(buf, &i, "* c-shouts:") ||
2745                                            looking_at(buf, &i, "--> * ") ||
2746                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2747                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2748                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2749                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2750                 int p;
2751                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2752                 chattingPartner = -1;
2753
2754                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2755                 for(p=0; p<MAX_CHAT; p++) {
2756                     if(channel == atoi(chatPartner[p])) {
2757                     talker[0] = '['; strcat(talker, "] ");
2758                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2759                     chattingPartner = p; break;
2760                     }
2761                 } else
2762                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2763                 for(p=0; p<MAX_CHAT; p++) {
2764                     if(!strcmp("whispers", chatPartner[p])) {
2765                         talker[0] = '['; strcat(talker, "] ");
2766                         chattingPartner = p; break;
2767                     }
2768                 } else
2769                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2770                 for(p=0; p<MAX_CHAT; p++) {
2771                     if(!strcmp("shouts", chatPartner[p])) {
2772                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2773                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2774                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2775                         chattingPartner = p; break;
2776                     }
2777                 }
2778                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2779                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2780                     talker[0] = 0; Colorize(ColorTell, FALSE);
2781                     chattingPartner = p; break;
2782                 }
2783                 if(chattingPartner<0) i = oldi; else {
2784                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2785                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2786                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2787                     started = STARTED_COMMENT;
2788                     parse_pos = 0; parse[0] = NULLCHAR;
2789                     savingComment = 3 + chattingPartner; // counts as TRUE
2790                     suppressKibitz = TRUE;
2791                     continue;
2792                 }
2793             } // [HGM] chat: end of patch
2794
2795             if (appData.zippyTalk || appData.zippyPlay) {
2796                 /* [DM] Backup address for color zippy lines */
2797                 backup = i;
2798 #if ZIPPY
2799        #ifdef WIN32
2800                if (loggedOn == TRUE)
2801                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2802                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2803        #else
2804                 if (ZippyControl(buf, &i) ||
2805                     ZippyConverse(buf, &i) ||
2806                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2807                       loggedOn = TRUE;
2808                       if (!appData.colorize) continue;
2809                 }
2810        #endif
2811 #endif
2812             } // [DM] 'else { ' deleted
2813                 if (
2814                     /* Regular tells and says */
2815                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2816                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2817                     looking_at(buf, &i, "* says: ") ||
2818                     /* Don't color "message" or "messages" output */
2819                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2820                     looking_at(buf, &i, "*. * at *:*: ") ||
2821                     looking_at(buf, &i, "--* (*:*): ") ||
2822                     /* Message notifications (same color as tells) */
2823                     looking_at(buf, &i, "* has left a message ") ||
2824                     looking_at(buf, &i, "* just sent you a message:\n") ||
2825                     /* Whispers and kibitzes */
2826                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2827                     looking_at(buf, &i, "* kibitzes: ") ||
2828                     /* Channel tells */
2829                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2830
2831                   if (tkind == 1 && strchr(star_match[0], ':')) {
2832                       /* Avoid "tells you:" spoofs in channels */
2833                      tkind = 3;
2834                   }
2835                   if (star_match[0][0] == NULLCHAR ||
2836                       strchr(star_match[0], ' ') ||
2837                       (tkind == 3 && strchr(star_match[1], ' '))) {
2838                     /* Reject bogus matches */
2839                     i = oldi;
2840                   } else {
2841                     if (appData.colorize) {
2842                       if (oldi > next_out) {
2843                         SendToPlayer(&buf[next_out], oldi - next_out);
2844                         next_out = oldi;
2845                       }
2846                       switch (tkind) {
2847                       case 1:
2848                         Colorize(ColorTell, FALSE);
2849                         curColor = ColorTell;
2850                         break;
2851                       case 2:
2852                         Colorize(ColorKibitz, FALSE);
2853                         curColor = ColorKibitz;
2854                         break;
2855                       case 3:
2856                         p = strrchr(star_match[1], '(');
2857                         if (p == NULL) {
2858                           p = star_match[1];
2859                         } else {
2860                           p++;
2861                         }
2862                         if (atoi(p) == 1) {
2863                           Colorize(ColorChannel1, FALSE);
2864                           curColor = ColorChannel1;
2865                         } else {
2866                           Colorize(ColorChannel, FALSE);
2867                           curColor = ColorChannel;
2868                         }
2869                         break;
2870                       case 5:
2871                         curColor = ColorNormal;
2872                         break;
2873                       }
2874                     }
2875                     if (started == STARTED_NONE && appData.autoComment &&
2876                         (gameMode == IcsObserving ||
2877                          gameMode == IcsPlayingWhite ||
2878                          gameMode == IcsPlayingBlack)) {
2879                       parse_pos = i - oldi;
2880                       memcpy(parse, &buf[oldi], parse_pos);
2881                       parse[parse_pos] = NULLCHAR;
2882                       started = STARTED_COMMENT;
2883                       savingComment = TRUE;
2884                     } else {
2885                       started = STARTED_CHATTER;
2886                       savingComment = FALSE;
2887                     }
2888                     loggedOn = TRUE;
2889                     continue;
2890                   }
2891                 }
2892
2893                 if (looking_at(buf, &i, "* s-shouts: ") ||
2894                     looking_at(buf, &i, "* c-shouts: ")) {
2895                     if (appData.colorize) {
2896                         if (oldi > next_out) {
2897                             SendToPlayer(&buf[next_out], oldi - next_out);
2898                             next_out = oldi;
2899                         }
2900                         Colorize(ColorSShout, FALSE);
2901                         curColor = ColorSShout;
2902                     }
2903                     loggedOn = TRUE;
2904                     started = STARTED_CHATTER;
2905                     continue;
2906                 }
2907
2908                 if (looking_at(buf, &i, "--->")) {
2909                     loggedOn = TRUE;
2910                     continue;
2911                 }
2912
2913                 if (looking_at(buf, &i, "* shouts: ") ||
2914                     looking_at(buf, &i, "--> ")) {
2915                     if (appData.colorize) {
2916                         if (oldi > next_out) {
2917                             SendToPlayer(&buf[next_out], oldi - next_out);
2918                             next_out = oldi;
2919                         }
2920                         Colorize(ColorShout, FALSE);
2921                         curColor = ColorShout;
2922                     }
2923                     loggedOn = TRUE;
2924                     started = STARTED_CHATTER;
2925                     continue;
2926                 }
2927
2928                 if (looking_at( buf, &i, "Challenge:")) {
2929                     if (appData.colorize) {
2930                         if (oldi > next_out) {
2931                             SendToPlayer(&buf[next_out], oldi - next_out);
2932                             next_out = oldi;
2933                         }
2934                         Colorize(ColorChallenge, FALSE);
2935                         curColor = ColorChallenge;
2936                     }
2937                     loggedOn = TRUE;
2938                     continue;
2939                 }
2940
2941                 if (looking_at(buf, &i, "* offers you") ||
2942                     looking_at(buf, &i, "* offers to be") ||
2943                     looking_at(buf, &i, "* would like to") ||
2944                     looking_at(buf, &i, "* requests to") ||
2945                     looking_at(buf, &i, "Your opponent offers") ||
2946                     looking_at(buf, &i, "Your opponent requests")) {
2947
2948                     if (appData.colorize) {
2949                         if (oldi > next_out) {
2950                             SendToPlayer(&buf[next_out], oldi - next_out);
2951                             next_out = oldi;
2952                         }
2953                         Colorize(ColorRequest, FALSE);
2954                         curColor = ColorRequest;
2955                     }
2956                     continue;
2957                 }
2958
2959                 if (looking_at(buf, &i, "* (*) seeking")) {
2960                     if (appData.colorize) {
2961                         if (oldi > next_out) {
2962                             SendToPlayer(&buf[next_out], oldi - next_out);
2963                             next_out = oldi;
2964                         }
2965                         Colorize(ColorSeek, FALSE);
2966                         curColor = ColorSeek;
2967                     }
2968                     continue;
2969             }
2970
2971             if (looking_at(buf, &i, "\\   ")) {
2972                 if (prevColor != ColorNormal) {
2973                     if (oldi > next_out) {
2974                         SendToPlayer(&buf[next_out], oldi - next_out);
2975                         next_out = oldi;
2976                     }
2977                     Colorize(prevColor, TRUE);
2978                     curColor = prevColor;
2979                 }
2980                 if (savingComment) {
2981                     parse_pos = i - oldi;
2982                     memcpy(parse, &buf[oldi], parse_pos);
2983                     parse[parse_pos] = NULLCHAR;
2984                     started = STARTED_COMMENT;
2985                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2986                         chattingPartner = savingComment - 3; // kludge to remember the box
2987                 } else {
2988                     started = STARTED_CHATTER;
2989                 }
2990                 continue;
2991             }
2992
2993             if (looking_at(buf, &i, "Black Strength :") ||
2994                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2995                 looking_at(buf, &i, "<10>") ||
2996                 looking_at(buf, &i, "#@#")) {
2997                 /* Wrong board style */
2998                 loggedOn = TRUE;
2999                 SendToICS(ics_prefix);
3000                 SendToICS("set style 12\n");
3001                 SendToICS(ics_prefix);
3002                 SendToICS("refresh\n");
3003                 continue;
3004             }
3005             
3006             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3007                 ICSInitScript();
3008                 have_sent_ICS_logon = 1;
3009                 continue;
3010             }
3011               
3012             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3013                 (looking_at(buf, &i, "\n<12> ") ||
3014                  looking_at(buf, &i, "<12> "))) {
3015                 loggedOn = TRUE;
3016                 if (oldi > next_out) {
3017                     SendToPlayer(&buf[next_out], oldi - next_out);
3018                 }
3019                 next_out = i;
3020                 started = STARTED_BOARD;
3021                 parse_pos = 0;
3022                 continue;
3023             }
3024
3025             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3026                 looking_at(buf, &i, "<b1> ")) {
3027                 if (oldi > next_out) {
3028                     SendToPlayer(&buf[next_out], oldi - next_out);
3029                 }
3030                 next_out = i;
3031                 started = STARTED_HOLDINGS;
3032                 parse_pos = 0;
3033                 continue;
3034             }
3035
3036             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3037                 loggedOn = TRUE;
3038                 /* Header for a move list -- first line */
3039
3040                 switch (ics_getting_history) {
3041                   case H_FALSE:
3042                     switch (gameMode) {
3043                       case IcsIdle:
3044                       case BeginningOfGame:
3045                         /* User typed "moves" or "oldmoves" while we
3046                            were idle.  Pretend we asked for these
3047                            moves and soak them up so user can step
3048                            through them and/or save them.
3049                            */
3050                         Reset(FALSE, TRUE);
3051                         gameMode = IcsObserving;
3052                         ModeHighlight();
3053                         ics_gamenum = -1;
3054                         ics_getting_history = H_GOT_UNREQ_HEADER;
3055                         break;
3056                       case EditGame: /*?*/
3057                       case EditPosition: /*?*/
3058                         /* Should above feature work in these modes too? */
3059                         /* For now it doesn't */
3060                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3061                         break;
3062                       default:
3063                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3064                         break;
3065                     }
3066                     break;
3067                   case H_REQUESTED:
3068                     /* Is this the right one? */
3069                     if (gameInfo.white && gameInfo.black &&
3070                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3071                         strcmp(gameInfo.black, star_match[2]) == 0) {
3072                         /* All is well */
3073                         ics_getting_history = H_GOT_REQ_HEADER;
3074                     }
3075                     break;
3076                   case H_GOT_REQ_HEADER:
3077                   case H_GOT_UNREQ_HEADER:
3078                   case H_GOT_UNWANTED_HEADER:
3079                   case H_GETTING_MOVES:
3080                     /* Should not happen */
3081                     DisplayError(_("Error gathering move list: two headers"), 0);
3082                     ics_getting_history = H_FALSE;
3083                     break;
3084                 }
3085
3086                 /* Save player ratings into gameInfo if needed */
3087                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3088                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3089                     (gameInfo.whiteRating == -1 ||
3090                      gameInfo.blackRating == -1)) {
3091
3092                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3093                     gameInfo.blackRating = string_to_rating(star_match[3]);
3094                     if (appData.debugMode)
3095                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3096                               gameInfo.whiteRating, gameInfo.blackRating);
3097                 }
3098                 continue;
3099             }
3100
3101             if (looking_at(buf, &i,
3102               "* * match, initial time: * minute*, increment: * second")) {
3103                 /* Header for a move list -- second line */
3104                 /* Initial board will follow if this is a wild game */
3105                 if (gameInfo.event != NULL) free(gameInfo.event);
3106                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3107                 gameInfo.event = StrSave(str);
3108                 /* [HGM] we switched variant. Translate boards if needed. */
3109                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3110                 continue;
3111             }
3112
3113             if (looking_at(buf, &i, "Move  ")) {
3114                 /* Beginning of a move list */
3115                 switch (ics_getting_history) {
3116                   case H_FALSE:
3117                     /* Normally should not happen */
3118                     /* Maybe user hit reset while we were parsing */
3119                     break;
3120                   case H_REQUESTED:
3121                     /* Happens if we are ignoring a move list that is not
3122                      * the one we just requested.  Common if the user
3123                      * tries to observe two games without turning off
3124                      * getMoveList */
3125                     break;
3126                   case H_GETTING_MOVES:
3127                     /* Should not happen */
3128                     DisplayError(_("Error gathering move list: nested"), 0);
3129                     ics_getting_history = H_FALSE;
3130                     break;
3131                   case H_GOT_REQ_HEADER:
3132                     ics_getting_history = H_GETTING_MOVES;
3133                     started = STARTED_MOVES;
3134                     parse_pos = 0;
3135                     if (oldi > next_out) {
3136                         SendToPlayer(&buf[next_out], oldi - next_out);
3137                     }
3138                     break;
3139                   case H_GOT_UNREQ_HEADER:
3140                     ics_getting_history = H_GETTING_MOVES;
3141                     started = STARTED_MOVES_NOHIDE;
3142                     parse_pos = 0;
3143                     break;
3144                   case H_GOT_UNWANTED_HEADER:
3145                     ics_getting_history = H_FALSE;
3146                     break;
3147                 }
3148                 continue;
3149             }                           
3150             
3151             if (looking_at(buf, &i, "% ") ||
3152                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3153                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3154                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3155                     soughtPending = FALSE;
3156                     seekGraphUp = TRUE;
3157                     DrawSeekGraph();
3158                 }
3159                 if(suppressKibitz) next_out = i;
3160                 savingComment = FALSE;
3161                 suppressKibitz = 0;
3162                 switch (started) {
3163                   case STARTED_MOVES:
3164                   case STARTED_MOVES_NOHIDE:
3165                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3166                     parse[parse_pos + i - oldi] = NULLCHAR;
3167                     ParseGameHistory(parse);
3168 #if ZIPPY
3169                     if (appData.zippyPlay && first.initDone) {
3170                         FeedMovesToProgram(&first, forwardMostMove);
3171                         if (gameMode == IcsPlayingWhite) {
3172                             if (WhiteOnMove(forwardMostMove)) {
3173                                 if (first.sendTime) {
3174                                   if (first.useColors) {
3175                                     SendToProgram("black\n", &first); 
3176                                   }
3177                                   SendTimeRemaining(&first, TRUE);
3178                                 }
3179                                 if (first.useColors) {
3180                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3181                                 }
3182                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3183                                 first.maybeThinking = TRUE;
3184                             } else {
3185                                 if (first.usePlayother) {
3186                                   if (first.sendTime) {
3187                                     SendTimeRemaining(&first, TRUE);
3188                                   }
3189                                   SendToProgram("playother\n", &first);
3190                                   firstMove = FALSE;
3191                                 } else {
3192                                   firstMove = TRUE;
3193                                 }
3194                             }
3195                         } else if (gameMode == IcsPlayingBlack) {
3196                             if (!WhiteOnMove(forwardMostMove)) {
3197                                 if (first.sendTime) {
3198                                   if (first.useColors) {
3199                                     SendToProgram("white\n", &first);
3200                                   }
3201                                   SendTimeRemaining(&first, FALSE);
3202                                 }
3203                                 if (first.useColors) {
3204                                   SendToProgram("black\n", &first);
3205                                 }
3206                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3207                                 first.maybeThinking = TRUE;
3208                             } else {
3209                                 if (first.usePlayother) {
3210                                   if (first.sendTime) {
3211                                     SendTimeRemaining(&first, FALSE);
3212                                   }
3213                                   SendToProgram("playother\n", &first);
3214                                   firstMove = FALSE;
3215                                 } else {
3216                                   firstMove = TRUE;
3217                                 }
3218                             }
3219                         }                       
3220                     }
3221 #endif
3222                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3223                         /* Moves came from oldmoves or moves command
3224                            while we weren't doing anything else.
3225                            */
3226                         currentMove = forwardMostMove;
3227                         ClearHighlights();/*!!could figure this out*/
3228                         flipView = appData.flipView;
3229                         DrawPosition(TRUE, boards[currentMove]);
3230                         DisplayBothClocks();
3231                         sprintf(str, "%s vs. %s",
3232                                 gameInfo.white, gameInfo.black);
3233                         DisplayTitle(str);
3234                         gameMode = IcsIdle;
3235                     } else {
3236                         /* Moves were history of an active game */
3237                         if (gameInfo.resultDetails != NULL) {
3238                             free(gameInfo.resultDetails);
3239                             gameInfo.resultDetails = NULL;
3240                         }
3241                     }
3242                     HistorySet(parseList, backwardMostMove,
3243                                forwardMostMove, currentMove-1);
3244                     DisplayMove(currentMove - 1);
3245                     if (started == STARTED_MOVES) next_out = i;
3246                     started = STARTED_NONE;
3247                     ics_getting_history = H_FALSE;
3248                     break;
3249
3250                   case STARTED_OBSERVE:
3251                     started = STARTED_NONE;
3252                     SendToICS(ics_prefix);
3253                     SendToICS("refresh\n");
3254                     break;
3255
3256                   default:
3257                     break;
3258                 }
3259                 if(bookHit) { // [HGM] book: simulate book reply
3260                     static char bookMove[MSG_SIZ]; // a bit generous?
3261
3262                     programStats.nodes = programStats.depth = programStats.time = 
3263                     programStats.score = programStats.got_only_move = 0;
3264                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3265
3266                     strcpy(bookMove, "move ");
3267                     strcat(bookMove, bookHit);
3268                     HandleMachineMove(bookMove, &first);
3269                 }
3270                 continue;
3271             }
3272             
3273             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3274                  started == STARTED_HOLDINGS ||
3275                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3276                 /* Accumulate characters in move list or board */
3277                 parse[parse_pos++] = buf[i];
3278             }
3279             
3280             /* Start of game messages.  Mostly we detect start of game
3281                when the first board image arrives.  On some versions
3282                of the ICS, though, we need to do a "refresh" after starting
3283                to observe in order to get the current board right away. */
3284             if (looking_at(buf, &i, "Adding game * to observation list")) {
3285                 started = STARTED_OBSERVE;
3286                 continue;
3287             }
3288
3289             /* Handle auto-observe */
3290             if (appData.autoObserve &&
3291                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3292                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3293                 char *player;
3294                 /* Choose the player that was highlighted, if any. */
3295                 if (star_match[0][0] == '\033' ||
3296                     star_match[1][0] != '\033') {
3297                     player = star_match[0];
3298                 } else {
3299                     player = star_match[2];
3300                 }
3301                 sprintf(str, "%sobserve %s\n",
3302                         ics_prefix, StripHighlightAndTitle(player));
3303                 SendToICS(str);
3304
3305                 /* Save ratings from notify string */
3306                 strcpy(player1Name, star_match[0]);
3307                 player1Rating = string_to_rating(star_match[1]);
3308                 strcpy(player2Name, star_match[2]);
3309                 player2Rating = string_to_rating(star_match[3]);
3310
3311                 if (appData.debugMode)
3312                   fprintf(debugFP, 
3313                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3314                           player1Name, player1Rating,
3315                           player2Name, player2Rating);
3316
3317                 continue;
3318             }
3319
3320             /* Deal with automatic examine mode after a game,
3321                and with IcsObserving -> IcsExamining transition */
3322             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3323                 looking_at(buf, &i, "has made you an examiner of game *")) {
3324
3325                 int gamenum = atoi(star_match[0]);
3326                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3327                     gamenum == ics_gamenum) {
3328                     /* We were already playing or observing this game;
3329                        no need to refetch history */
3330                     gameMode = IcsExamining;
3331                     if (pausing) {
3332                         pauseExamForwardMostMove = forwardMostMove;
3333                     } else if (currentMove < forwardMostMove) {
3334                         ForwardInner(forwardMostMove);
3335                     }
3336                 } else {
3337                     /* I don't think this case really can happen */
3338                     SendToICS(ics_prefix);
3339                     SendToICS("refresh\n");
3340                 }
3341                 continue;
3342             }    
3343             
3344             /* Error messages */
3345 //          if (ics_user_moved) {
3346             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3347                 if (looking_at(buf, &i, "Illegal move") ||
3348                     looking_at(buf, &i, "Not a legal move") ||
3349                     looking_at(buf, &i, "Your king is in check") ||
3350                     looking_at(buf, &i, "It isn't your turn") ||
3351                     looking_at(buf, &i, "It is not your move")) {
3352                     /* Illegal move */
3353                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3354                         currentMove = forwardMostMove-1;
3355                         DisplayMove(currentMove - 1); /* before DMError */
3356                         DrawPosition(FALSE, boards[currentMove]);
3357                         SwitchClocks(forwardMostMove-1); // [HGM] race
3358                         DisplayBothClocks();
3359                     }
3360                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3361                     ics_user_moved = 0;
3362                     continue;
3363                 }
3364             }
3365
3366             if (looking_at(buf, &i, "still have time") ||
3367                 looking_at(buf, &i, "not out of time") ||
3368                 looking_at(buf, &i, "either player is out of time") ||
3369                 looking_at(buf, &i, "has timeseal; checking")) {
3370                 /* We must have called his flag a little too soon */
3371                 whiteFlag = blackFlag = FALSE;
3372                 continue;
3373             }
3374
3375             if (looking_at(buf, &i, "added * seconds to") ||
3376                 looking_at(buf, &i, "seconds were added to")) {
3377                 /* Update the clocks */
3378                 SendToICS(ics_prefix);
3379                 SendToICS("refresh\n");
3380                 continue;
3381             }
3382
3383             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3384                 ics_clock_paused = TRUE;
3385                 StopClocks();
3386                 continue;
3387             }
3388
3389             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3390                 ics_clock_paused = FALSE;
3391                 StartClocks();
3392                 continue;
3393             }
3394
3395             /* Grab player ratings from the Creating: message.
3396                Note we have to check for the special case when
3397                the ICS inserts things like [white] or [black]. */
3398             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3399                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3400                 /* star_matches:
3401                    0    player 1 name (not necessarily white)
3402                    1    player 1 rating
3403                    2    empty, white, or black (IGNORED)
3404                    3    player 2 name (not necessarily black)
3405                    4    player 2 rating
3406                    
3407                    The names/ratings are sorted out when the game
3408                    actually starts (below).
3409                 */
3410                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3411                 player1Rating = string_to_rating(star_match[1]);
3412                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3413                 player2Rating = string_to_rating(star_match[4]);
3414
3415                 if (appData.debugMode)
3416                   fprintf(debugFP, 
3417                           "Ratings from 'Creating:' %s %d, %s %d\n",
3418                           player1Name, player1Rating,
3419                           player2Name, player2Rating);
3420
3421                 continue;
3422             }
3423             
3424             /* Improved generic start/end-of-game messages */
3425             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3426                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3427                 /* If tkind == 0: */
3428                 /* star_match[0] is the game number */
3429                 /*           [1] is the white player's name */
3430                 /*           [2] is the black player's name */
3431                 /* For end-of-game: */
3432                 /*           [3] is the reason for the game end */
3433                 /*           [4] is a PGN end game-token, preceded by " " */
3434                 /* For start-of-game: */
3435                 /*           [3] begins with "Creating" or "Continuing" */
3436                 /*           [4] is " *" or empty (don't care). */
3437                 int gamenum = atoi(star_match[0]);
3438                 char *whitename, *blackname, *why, *endtoken;
3439                 ChessMove endtype = (ChessMove) 0;
3440
3441                 if (tkind == 0) {
3442                   whitename = star_match[1];
3443                   blackname = star_match[2];
3444                   why = star_match[3];
3445                   endtoken = star_match[4];
3446                 } else {
3447                   whitename = star_match[1];
3448                   blackname = star_match[3];
3449                   why = star_match[5];
3450                   endtoken = star_match[6];
3451                 }
3452
3453                 /* Game start messages */
3454                 if (strncmp(why, "Creating ", 9) == 0 ||
3455                     strncmp(why, "Continuing ", 11) == 0) {
3456                     gs_gamenum = gamenum;
3457                     strcpy(gs_kind, strchr(why, ' ') + 1);
3458                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3459 #if ZIPPY
3460                     if (appData.zippyPlay) {
3461                         ZippyGameStart(whitename, blackname);
3462                     }
3463 #endif /*ZIPPY*/
3464                     continue;
3465                 }
3466
3467                 /* Game end messages */
3468                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3469                     ics_gamenum != gamenum) {
3470                     continue;
3471                 }
3472                 while (endtoken[0] == ' ') endtoken++;
3473                 switch (endtoken[0]) {
3474                   case '*':
3475                   default:
3476                     endtype = GameUnfinished;
3477                     break;
3478                   case '0':
3479                     endtype = BlackWins;
3480                     break;
3481                   case '1':
3482                     if (endtoken[1] == '/')
3483                       endtype = GameIsDrawn;
3484                     else
3485                       endtype = WhiteWins;
3486                     break;
3487                 }
3488                 GameEnds(endtype, why, GE_ICS);
3489 #if ZIPPY
3490                 if (appData.zippyPlay && first.initDone) {
3491                     ZippyGameEnd(endtype, why);
3492                     if (first.pr == NULL) {
3493                       /* Start the next process early so that we'll
3494                          be ready for the next challenge */
3495                       StartChessProgram(&first);
3496                     }
3497                     /* Send "new" early, in case this command takes
3498                        a long time to finish, so that we'll be ready
3499                        for the next challenge. */
3500                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3501                     Reset(TRUE, TRUE);
3502                 }
3503 #endif /*ZIPPY*/
3504                 continue;
3505             }
3506
3507             if (looking_at(buf, &i, "Removing game * from observation") ||
3508                 looking_at(buf, &i, "no longer observing game *") ||
3509                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3510                 if (gameMode == IcsObserving &&
3511                     atoi(star_match[0]) == ics_gamenum)
3512                   {
3513                       /* icsEngineAnalyze */
3514                       if (appData.icsEngineAnalyze) {
3515                             ExitAnalyzeMode();
3516                             ModeHighlight();
3517                       }
3518                       StopClocks();
3519                       gameMode = IcsIdle;
3520                       ics_gamenum = -1;
3521                       ics_user_moved = FALSE;
3522                   }
3523                 continue;
3524             }
3525
3526             if (looking_at(buf, &i, "no longer examining game *")) {
3527                 if (gameMode == IcsExamining &&
3528                     atoi(star_match[0]) == ics_gamenum)
3529                   {
3530                       gameMode = IcsIdle;
3531                       ics_gamenum = -1;
3532                       ics_user_moved = FALSE;
3533                   }
3534                 continue;
3535             }
3536
3537             /* Advance leftover_start past any newlines we find,
3538                so only partial lines can get reparsed */
3539             if (looking_at(buf, &i, "\n")) {
3540                 prevColor = curColor;
3541                 if (curColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(ColorNormal, FALSE);
3547                     curColor = ColorNormal;
3548                 }
3549                 if (started == STARTED_BOARD) {
3550                     started = STARTED_NONE;
3551                     parse[parse_pos] = NULLCHAR;
3552                     ParseBoard12(parse);
3553                     ics_user_moved = 0;
3554
3555                     /* Send premove here */
3556                     if (appData.premove) {
3557                       char str[MSG_SIZ];
3558                       if (currentMove == 0 &&
3559                           gameMode == IcsPlayingWhite &&
3560                           appData.premoveWhite) {
3561                         sprintf(str, "%s\n", appData.premoveWhiteText);
3562                         if (appData.debugMode)
3563                           fprintf(debugFP, "Sending premove:\n");
3564                         SendToICS(str);
3565                       } else if (currentMove == 1 &&
3566                                  gameMode == IcsPlayingBlack &&
3567                                  appData.premoveBlack) {
3568                         sprintf(str, "%s\n", appData.premoveBlackText);
3569                         if (appData.debugMode)
3570                           fprintf(debugFP, "Sending premove:\n");
3571                         SendToICS(str);
3572                       } else if (gotPremove) {
3573                         gotPremove = 0;
3574                         ClearPremoveHighlights();
3575                         if (appData.debugMode)
3576                           fprintf(debugFP, "Sending premove:\n");
3577                           UserMoveEvent(premoveFromX, premoveFromY, 
3578                                         premoveToX, premoveToY, 
3579                                         premovePromoChar);
3580                       }
3581                     }
3582
3583                     /* Usually suppress following prompt */
3584                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3585                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3586                         if (looking_at(buf, &i, "*% ")) {
3587                             savingComment = FALSE;
3588                             suppressKibitz = 0;
3589                         }
3590                     }
3591                     next_out = i;
3592                 } else if (started == STARTED_HOLDINGS) {
3593                     int gamenum;
3594                     char new_piece[MSG_SIZ];
3595                     started = STARTED_NONE;
3596                     parse[parse_pos] = NULLCHAR;
3597                     if (appData.debugMode)
3598                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3599                                                         parse, currentMove);
3600                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3601                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3602                         if (gameInfo.variant == VariantNormal) {
3603                           /* [HGM] We seem to switch variant during a game!
3604                            * Presumably no holdings were displayed, so we have
3605                            * to move the position two files to the right to
3606                            * create room for them!
3607                            */
3608                           VariantClass newVariant;
3609                           switch(gameInfo.boardWidth) { // base guess on board width
3610                                 case 9:  newVariant = VariantShogi; break;
3611                                 case 10: newVariant = VariantGreat; break;
3612                                 default: newVariant = VariantCrazyhouse; break;
3613                           }
3614                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3615                           /* Get a move list just to see the header, which
3616                              will tell us whether this is really bug or zh */
3617                           if (ics_getting_history == H_FALSE) {
3618                             ics_getting_history = H_REQUESTED;
3619                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3620                             SendToICS(str);
3621                           }
3622                         }
3623                         new_piece[0] = NULLCHAR;
3624                         sscanf(parse, "game %d white [%s black [%s <- %s",
3625                                &gamenum, white_holding, black_holding,
3626                                new_piece);
3627                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3628                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3629                         /* [HGM] copy holdings to board holdings area */
3630                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3631                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3632                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3633 #if ZIPPY
3634                         if (appData.zippyPlay && first.initDone) {
3635                             ZippyHoldings(white_holding, black_holding,
3636                                           new_piece);
3637                         }
3638 #endif /*ZIPPY*/
3639                         if (tinyLayout || smallLayout) {
3640                             char wh[16], bh[16];
3641                             PackHolding(wh, white_holding);
3642                             PackHolding(bh, black_holding);
3643                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3644                                     gameInfo.white, gameInfo.black);
3645                         } else {
3646                             sprintf(str, "%s [%s] vs. %s [%s]",
3647                                     gameInfo.white, white_holding,
3648                                     gameInfo.black, black_holding);
3649                         }
3650
3651                         DrawPosition(FALSE, boards[currentMove]);
3652                         DisplayTitle(str);
3653                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3654                         sscanf(parse, "game %d white [%s black [%s <- %s",
3655                                &gamenum, white_holding, black_holding,
3656                                new_piece);
3657                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3658                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3659                         /* [HGM] copy holdings to partner-board holdings area */
3660                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3661                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3662                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3663                       }
3664                     }
3665                     /* Suppress following prompt */
3666                     if (looking_at(buf, &i, "*% ")) {
3667                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3668                         savingComment = FALSE;
3669                         suppressKibitz = 0;
3670                     }
3671                     next_out = i;
3672                 }
3673                 continue;
3674             }
3675
3676             i++;                /* skip unparsed character and loop back */
3677         }
3678         
3679         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3680 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3681 //          SendToPlayer(&buf[next_out], i - next_out);
3682             started != STARTED_HOLDINGS && leftover_start > next_out) {
3683             SendToPlayer(&buf[next_out], leftover_start - next_out);
3684             next_out = i;
3685         }
3686         
3687         leftover_len = buf_len - leftover_start;
3688         /* if buffer ends with something we couldn't parse,
3689            reparse it after appending the next read */
3690         
3691     } else if (count == 0) {
3692         RemoveInputSource(isr);
3693         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3694     } else {
3695         DisplayFatalError(_("Error reading from ICS"), error, 1);
3696     }
3697 }
3698
3699
3700 /* Board style 12 looks like this:
3701    
3702    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3703    
3704  * The "<12> " is stripped before it gets to this routine.  The two
3705  * trailing 0's (flip state and clock ticking) are later addition, and
3706  * some chess servers may not have them, or may have only the first.
3707  * Additional trailing fields may be added in the future.  
3708  */
3709
3710 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3711
3712 #define RELATION_OBSERVING_PLAYED    0
3713 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3714 #define RELATION_PLAYING_MYMOVE      1
3715 #define RELATION_PLAYING_NOTMYMOVE  -1
3716 #define RELATION_EXAMINING           2
3717 #define RELATION_ISOLATED_BOARD     -3
3718 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3719
3720 void
3721 ParseBoard12(string)
3722      char *string;
3723
3724     GameMode newGameMode;
3725     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3726     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3727     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3728     char to_play, board_chars[200];
3729     char move_str[500], str[500], elapsed_time[500];
3730     char black[32], white[32];
3731     Board board;
3732     int prevMove = currentMove;
3733     int ticking = 2;
3734     ChessMove moveType;
3735     int fromX, fromY, toX, toY;
3736     char promoChar;
3737     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3738     char *bookHit = NULL; // [HGM] book
3739     Boolean weird = FALSE, reqFlag = FALSE;
3740
3741     fromX = fromY = toX = toY = -1;
3742     
3743     newGame = FALSE;
3744
3745     if (appData.debugMode)
3746       fprintf(debugFP, _("Parsing board: %s\n"), string);
3747
3748     move_str[0] = NULLCHAR;
3749     elapsed_time[0] = NULLCHAR;
3750     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3751         int  i = 0, j;
3752         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3753             if(string[i] == ' ') { ranks++; files = 0; }
3754             else files++;
3755             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3756             i++;
3757         }
3758         for(j = 0; j <i; j++) board_chars[j] = string[j];
3759         board_chars[i] = '\0';
3760         string += i + 1;
3761     }
3762     n = sscanf(string, PATTERN, &to_play, &double_push,
3763                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3764                &gamenum, white, black, &relation, &basetime, &increment,
3765                &white_stren, &black_stren, &white_time, &black_time,
3766                &moveNum, str, elapsed_time, move_str, &ics_flip,
3767                &ticking);
3768
3769     if (n < 21) {
3770         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3771         DisplayError(str, 0);
3772         return;
3773     }
3774
3775     /* Convert the move number to internal form */
3776     moveNum = (moveNum - 1) * 2;
3777     if (to_play == 'B') moveNum++;
3778     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3779       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3780                         0, 1);
3781       return;
3782     }
3783     
3784     switch (relation) {
3785       case RELATION_OBSERVING_PLAYED:
3786       case RELATION_OBSERVING_STATIC:
3787         if (gamenum == -1) {
3788             /* Old ICC buglet */
3789             relation = RELATION_OBSERVING_STATIC;
3790         }
3791         newGameMode = IcsObserving;
3792         break;
3793       case RELATION_PLAYING_MYMOVE:
3794       case RELATION_PLAYING_NOTMYMOVE:
3795         newGameMode =
3796           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3797             IcsPlayingWhite : IcsPlayingBlack;
3798         break;
3799       case RELATION_EXAMINING:
3800         newGameMode = IcsExamining;
3801         break;
3802       case RELATION_ISOLATED_BOARD:
3803       default:
3804         /* Just display this board.  If user was doing something else,
3805            we will forget about it until the next board comes. */ 
3806         newGameMode = IcsIdle;
3807         break;
3808       case RELATION_STARTING_POSITION:
3809         newGameMode = gameMode;
3810         break;
3811     }
3812     
3813     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3814          && newGameMode == IcsObserving && appData.bgObserve) {
3815       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3816       char buf[MSG_SIZ];
3817       for (k = 0; k < ranks; k++) {
3818         for (j = 0; j < files; j++)
3819           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3820         if(gameInfo.holdingsWidth > 1) {
3821              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3822              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3823         }
3824       }
3825       CopyBoard(partnerBoard, board);
3826       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3827       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3828                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3829       DisplayMessage(buf, "");
3830       return;
3831     }
3832
3833     /* Modify behavior for initial board display on move listing
3834        of wild games.
3835        */
3836     switch (ics_getting_history) {
3837       case H_FALSE:
3838       case H_REQUESTED:
3839         break;
3840       case H_GOT_REQ_HEADER:
3841       case H_GOT_UNREQ_HEADER:
3842         /* This is the initial position of the current game */
3843         gamenum = ics_gamenum;
3844         moveNum = 0;            /* old ICS bug workaround */
3845         if (to_play == 'B') {
3846           startedFromSetupPosition = TRUE;
3847           blackPlaysFirst = TRUE;
3848           moveNum = 1;
3849           if (forwardMostMove == 0) forwardMostMove = 1;
3850           if (backwardMostMove == 0) backwardMostMove = 1;
3851           if (currentMove == 0) currentMove = 1;
3852         }
3853         newGameMode = gameMode;
3854         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3855         break;
3856       case H_GOT_UNWANTED_HEADER:
3857         /* This is an initial board that we don't want */
3858         return;
3859       case H_GETTING_MOVES:
3860         /* Should not happen */
3861         DisplayError(_("Error gathering move list: extra board"), 0);
3862         ics_getting_history = H_FALSE;
3863         return;
3864     }
3865
3866    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3867                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3868      /* [HGM] We seem to have switched variant unexpectedly
3869       * Try to guess new variant from board size
3870       */
3871           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3872           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3873           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3874           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3875           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3876           if(!weird) newVariant = VariantNormal;
3877           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3878           /* Get a move list just to see the header, which
3879              will tell us whether this is really bug or zh */
3880           if (ics_getting_history == H_FALSE) {
3881             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3882             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3883             SendToICS(str);
3884           }
3885     }
3886     
3887     /* Take action if this is the first board of a new game, or of a
3888        different game than is currently being displayed.  */
3889     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3890         relation == RELATION_ISOLATED_BOARD) {
3891         
3892         /* Forget the old game and get the history (if any) of the new one */
3893         if (gameMode != BeginningOfGame) {
3894           Reset(TRUE, TRUE);
3895         }
3896         newGame = TRUE;
3897         if (appData.autoRaiseBoard) BoardToTop();
3898         prevMove = -3;
3899         if (gamenum == -1) {
3900             newGameMode = IcsIdle;
3901         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3902                    appData.getMoveList && !reqFlag) {
3903             /* Need to get game history */
3904             ics_getting_history = H_REQUESTED;
3905             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3906             SendToICS(str);
3907         }
3908         
3909         /* Initially flip the board to have black on the bottom if playing
3910            black or if the ICS flip flag is set, but let the user change
3911            it with the Flip View button. */
3912         flipView = appData.autoFlipView ? 
3913           (newGameMode == IcsPlayingBlack) || ics_flip :
3914           appData.flipView;
3915         
3916         /* Done with values from previous mode; copy in new ones */
3917         gameMode = newGameMode;
3918         ModeHighlight();
3919         ics_gamenum = gamenum;
3920         if (gamenum == gs_gamenum) {
3921             int klen = strlen(gs_kind);
3922             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3923             sprintf(str, "ICS %s", gs_kind);
3924             gameInfo.event = StrSave(str);
3925         } else {
3926             gameInfo.event = StrSave("ICS game");
3927         }
3928         gameInfo.site = StrSave(appData.icsHost);
3929         gameInfo.date = PGNDate();
3930         gameInfo.round = StrSave("-");
3931         gameInfo.white = StrSave(white);
3932         gameInfo.black = StrSave(black);
3933         timeControl = basetime * 60 * 1000;
3934         timeControl_2 = 0;
3935         timeIncrement = increment * 1000;
3936         movesPerSession = 0;
3937         gameInfo.timeControl = TimeControlTagValue();
3938         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3939   if (appData.debugMode) {
3940     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3941     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3942     setbuf(debugFP, NULL);
3943   }
3944
3945         gameInfo.outOfBook = NULL;
3946         
3947         /* Do we have the ratings? */
3948         if (strcmp(player1Name, white) == 0 &&
3949             strcmp(player2Name, black) == 0) {
3950             if (appData.debugMode)
3951               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3952                       player1Rating, player2Rating);
3953             gameInfo.whiteRating = player1Rating;
3954             gameInfo.blackRating = player2Rating;
3955         } else if (strcmp(player2Name, white) == 0 &&
3956                    strcmp(player1Name, black) == 0) {
3957             if (appData.debugMode)
3958               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3959                       player2Rating, player1Rating);
3960             gameInfo.whiteRating = player2Rating;
3961             gameInfo.blackRating = player1Rating;
3962         }
3963         player1Name[0] = player2Name[0] = NULLCHAR;
3964
3965         /* Silence shouts if requested */
3966         if (appData.quietPlay &&
3967             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3968             SendToICS(ics_prefix);
3969             SendToICS("set shout 0\n");
3970         }
3971     }
3972     
3973     /* Deal with midgame name changes */
3974     if (!newGame) {
3975         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3976             if (gameInfo.white) free(gameInfo.white);
3977             gameInfo.white = StrSave(white);
3978         }
3979         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3980             if (gameInfo.black) free(gameInfo.black);
3981             gameInfo.black = StrSave(black);
3982         }
3983     }
3984     
3985     /* Throw away game result if anything actually changes in examine mode */
3986     if (gameMode == IcsExamining && !newGame) {
3987         gameInfo.result = GameUnfinished;
3988         if (gameInfo.resultDetails != NULL) {
3989             free(gameInfo.resultDetails);
3990             gameInfo.resultDetails = NULL;
3991         }
3992     }
3993     
3994     /* In pausing && IcsExamining mode, we ignore boards coming
3995        in if they are in a different variation than we are. */
3996     if (pauseExamInvalid) return;
3997     if (pausing && gameMode == IcsExamining) {
3998         if (moveNum <= pauseExamForwardMostMove) {
3999             pauseExamInvalid = TRUE;
4000             forwardMostMove = pauseExamForwardMostMove;
4001             return;
4002         }
4003     }
4004     
4005   if (appData.debugMode) {
4006     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4007   }
4008     /* Parse the board */
4009     for (k = 0; k < ranks; k++) {
4010       for (j = 0; j < files; j++)
4011         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4012       if(gameInfo.holdingsWidth > 1) {
4013            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4014            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4015       }
4016     }
4017     CopyBoard(boards[moveNum], board);
4018     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4019     if (moveNum == 0) {
4020         startedFromSetupPosition =
4021           !CompareBoards(board, initialPosition);
4022         if(startedFromSetupPosition)
4023             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4024     }
4025
4026     /* [HGM] Set castling rights. Take the outermost Rooks,
4027        to make it also work for FRC opening positions. Note that board12
4028        is really defective for later FRC positions, as it has no way to
4029        indicate which Rook can castle if they are on the same side of King.
4030        For the initial position we grant rights to the outermost Rooks,
4031        and remember thos rights, and we then copy them on positions
4032        later in an FRC game. This means WB might not recognize castlings with
4033        Rooks that have moved back to their original position as illegal,
4034        but in ICS mode that is not its job anyway.
4035     */
4036     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4037     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4038
4039         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4040             if(board[0][i] == WhiteRook) j = i;
4041         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4042         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4043             if(board[0][i] == WhiteRook) j = i;
4044         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4045         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4046             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4047         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4048         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4049             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4050         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4051
4052         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4053         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4054             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4055         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4056             if(board[BOARD_HEIGHT-1][k] == bKing)
4057                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4058         if(gameInfo.variant == VariantTwoKings) {
4059             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4060             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4061             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4062         }
4063     } else { int r;
4064         r = boards[moveNum][CASTLING][0] = initialRights[0];
4065         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4066         r = boards[moveNum][CASTLING][1] = initialRights[1];
4067         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4068         r = boards[moveNum][CASTLING][3] = initialRights[3];
4069         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4070         r = boards[moveNum][CASTLING][4] = initialRights[4];
4071         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4072         /* wildcastle kludge: always assume King has rights */
4073         r = boards[moveNum][CASTLING][2] = initialRights[2];
4074         r = boards[moveNum][CASTLING][5] = initialRights[5];
4075     }
4076     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4077     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4078
4079     
4080     if (ics_getting_history == H_GOT_REQ_HEADER ||
4081         ics_getting_history == H_GOT_UNREQ_HEADER) {
4082         /* This was an initial position from a move list, not
4083            the current position */
4084         return;
4085     }
4086     
4087     /* Update currentMove and known move number limits */
4088     newMove = newGame || moveNum > forwardMostMove;
4089
4090     if (newGame) {
4091         forwardMostMove = backwardMostMove = currentMove = moveNum;
4092         if (gameMode == IcsExamining && moveNum == 0) {
4093           /* Workaround for ICS limitation: we are not told the wild
4094              type when starting to examine a game.  But if we ask for
4095              the move list, the move list header will tell us */
4096             ics_getting_history = H_REQUESTED;
4097             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4098             SendToICS(str);
4099         }
4100     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4101                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4102 #if ZIPPY
4103         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4104         /* [HGM] applied this also to an engine that is silently watching        */
4105         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4106             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4107             gameInfo.variant == currentlyInitializedVariant) {
4108           takeback = forwardMostMove - moveNum;
4109           for (i = 0; i < takeback; i++) {
4110             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4111             SendToProgram("undo\n", &first);
4112           }
4113         }
4114 #endif
4115
4116         forwardMostMove = moveNum;
4117         if (!pausing || currentMove > forwardMostMove)
4118           currentMove = forwardMostMove;
4119     } else {
4120         /* New part of history that is not contiguous with old part */ 
4121         if (pausing && gameMode == IcsExamining) {
4122             pauseExamInvalid = TRUE;
4123             forwardMostMove = pauseExamForwardMostMove;
4124             return;
4125         }
4126         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4127 #if ZIPPY
4128             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4129                 // [HGM] when we will receive the move list we now request, it will be
4130                 // fed to the engine from the first move on. So if the engine is not
4131                 // in the initial position now, bring it there.
4132                 InitChessProgram(&first, 0);
4133             }
4134 #endif
4135             ics_getting_history = H_REQUESTED;
4136             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4137             SendToICS(str);
4138         }
4139         forwardMostMove = backwardMostMove = currentMove = moveNum;
4140     }
4141     
4142     /* Update the clocks */
4143     if (strchr(elapsed_time, '.')) {
4144       /* Time is in ms */
4145       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4146       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4147     } else {
4148       /* Time is in seconds */
4149       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4150       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4151     }
4152       
4153
4154 #if ZIPPY
4155     if (appData.zippyPlay && newGame &&
4156         gameMode != IcsObserving && gameMode != IcsIdle &&
4157         gameMode != IcsExamining)
4158       ZippyFirstBoard(moveNum, basetime, increment);
4159 #endif
4160     
4161     /* Put the move on the move list, first converting
4162        to canonical algebraic form. */
4163     if (moveNum > 0) {
4164   if (appData.debugMode) {
4165     if (appData.debugMode) { int f = forwardMostMove;
4166         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4167                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4168                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4169     }
4170     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4171     fprintf(debugFP, "moveNum = %d\n", moveNum);
4172     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4173     setbuf(debugFP, NULL);
4174   }
4175         if (moveNum <= backwardMostMove) {
4176             /* We don't know what the board looked like before
4177                this move.  Punt. */
4178             strcpy(parseList[moveNum - 1], move_str);
4179             strcat(parseList[moveNum - 1], " ");
4180             strcat(parseList[moveNum - 1], elapsed_time);
4181             moveList[moveNum - 1][0] = NULLCHAR;
4182         } else if (strcmp(move_str, "none") == 0) {
4183             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4184             /* Again, we don't know what the board looked like;
4185                this is really the start of the game. */
4186             parseList[moveNum - 1][0] = NULLCHAR;
4187             moveList[moveNum - 1][0] = NULLCHAR;
4188             backwardMostMove = moveNum;
4189             startedFromSetupPosition = TRUE;
4190             fromX = fromY = toX = toY = -1;
4191         } else {
4192           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4193           //                 So we parse the long-algebraic move string in stead of the SAN move
4194           int valid; char buf[MSG_SIZ], *prom;
4195
4196           // str looks something like "Q/a1-a2"; kill the slash
4197           if(str[1] == '/') 
4198                 sprintf(buf, "%c%s", str[0], str+2);
4199           else  strcpy(buf, str); // might be castling
4200           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4201                 strcat(buf, prom); // long move lacks promo specification!
4202           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4203                 if(appData.debugMode) 
4204                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4205                 strcpy(move_str, buf);
4206           }
4207           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4208                                 &fromX, &fromY, &toX, &toY, &promoChar)
4209                || ParseOneMove(buf, moveNum - 1, &moveType,
4210                                 &fromX, &fromY, &toX, &toY, &promoChar);
4211           // end of long SAN patch
4212           if (valid) {
4213             (void) CoordsToAlgebraic(boards[moveNum - 1],
4214                                      PosFlags(moveNum - 1),
4215                                      fromY, fromX, toY, toX, promoChar,
4216                                      parseList[moveNum-1]);
4217             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4218               case MT_NONE:
4219               case MT_STALEMATE:
4220               default:
4221                 break;
4222               case MT_CHECK:
4223                 if(gameInfo.variant != VariantShogi)
4224                     strcat(parseList[moveNum - 1], "+");
4225                 break;
4226               case MT_CHECKMATE:
4227               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4228                 strcat(parseList[moveNum - 1], "#");
4229                 break;
4230             }
4231             strcat(parseList[moveNum - 1], " ");
4232             strcat(parseList[moveNum - 1], elapsed_time);
4233             /* currentMoveString is set as a side-effect of ParseOneMove */
4234             strcpy(moveList[moveNum - 1], currentMoveString);
4235             strcat(moveList[moveNum - 1], "\n");
4236           } else {
4237             /* Move from ICS was illegal!?  Punt. */
4238   if (appData.debugMode) {
4239     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4240     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4241   }
4242             strcpy(parseList[moveNum - 1], move_str);
4243             strcat(parseList[moveNum - 1], " ");
4244             strcat(parseList[moveNum - 1], elapsed_time);
4245             moveList[moveNum - 1][0] = NULLCHAR;
4246             fromX = fromY = toX = toY = -1;
4247           }
4248         }
4249   if (appData.debugMode) {
4250     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4251     setbuf(debugFP, NULL);
4252   }
4253
4254 #if ZIPPY
4255         /* Send move to chess program (BEFORE animating it). */
4256         if (appData.zippyPlay && !newGame && newMove && 
4257            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4258
4259             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4260                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4261                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4262                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4263                             move_str);
4264                     DisplayError(str, 0);
4265                 } else {
4266                     if (first.sendTime) {
4267                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4268                     }
4269                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4270                     if (firstMove && !bookHit) {
4271                         firstMove = FALSE;
4272                         if (first.useColors) {
4273                           SendToProgram(gameMode == IcsPlayingWhite ?
4274                                         "white\ngo\n" :
4275                                         "black\ngo\n", &first);
4276                         } else {
4277                           SendToProgram("go\n", &first);
4278                         }
4279                         first.maybeThinking = TRUE;
4280                     }
4281                 }
4282             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4283               if (moveList[moveNum - 1][0] == NULLCHAR) {
4284                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4285                 DisplayError(str, 0);
4286               } else {
4287                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4288                 SendMoveToProgram(moveNum - 1, &first);
4289               }
4290             }
4291         }
4292 #endif
4293     }
4294
4295     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4296         /* If move comes from a remote source, animate it.  If it
4297            isn't remote, it will have already been animated. */
4298         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4299             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4300         }
4301         if (!pausing && appData.highlightLastMove) {
4302             SetHighlights(fromX, fromY, toX, toY);
4303         }
4304     }
4305     
4306     /* Start the clocks */
4307     whiteFlag = blackFlag = FALSE;
4308     appData.clockMode = !(basetime == 0 && increment == 0);
4309     if (ticking == 0) {
4310       ics_clock_paused = TRUE;
4311       StopClocks();
4312     } else if (ticking == 1) {
4313       ics_clock_paused = FALSE;
4314     }
4315     if (gameMode == IcsIdle ||
4316         relation == RELATION_OBSERVING_STATIC ||
4317         relation == RELATION_EXAMINING ||
4318         ics_clock_paused)
4319       DisplayBothClocks();
4320     else
4321       StartClocks();
4322     
4323     /* Display opponents and material strengths */
4324     if (gameInfo.variant != VariantBughouse &&
4325         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4326         if (tinyLayout || smallLayout) {
4327             if(gameInfo.variant == VariantNormal)
4328                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4329                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4330                     basetime, increment);
4331             else
4332                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4333                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4334                     basetime, increment, (int) gameInfo.variant);
4335         } else {
4336             if(gameInfo.variant == VariantNormal)
4337                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4338                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4339                     basetime, increment);
4340             else
4341                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4342                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4343                     basetime, increment, VariantName(gameInfo.variant));
4344         }
4345         DisplayTitle(str);
4346   if (appData.debugMode) {
4347     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4348   }
4349     }
4350
4351
4352     /* Display the board */
4353     if (!pausing && !appData.noGUI) {
4354       
4355       if (appData.premove)
4356           if (!gotPremove || 
4357              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4358              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4359               ClearPremoveHighlights();
4360
4361       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4362       DrawPosition(j, boards[currentMove]);
4363
4364       DisplayMove(moveNum - 1);
4365       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4366             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4367               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4368         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4369       }
4370     }
4371
4372     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4373 #if ZIPPY
4374     if(bookHit) { // [HGM] book: simulate book reply
4375         static char bookMove[MSG_SIZ]; // a bit generous?
4376
4377         programStats.nodes = programStats.depth = programStats.time = 
4378         programStats.score = programStats.got_only_move = 0;
4379         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4380
4381         strcpy(bookMove, "move ");
4382         strcat(bookMove, bookHit);
4383         HandleMachineMove(bookMove, &first);
4384     }
4385 #endif
4386 }
4387
4388 void
4389 GetMoveListEvent()
4390 {
4391     char buf[MSG_SIZ];
4392     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4393         ics_getting_history = H_REQUESTED;
4394         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4395         SendToICS(buf);
4396     }
4397 }
4398
4399 void
4400 AnalysisPeriodicEvent(force)
4401      int force;
4402 {
4403     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4404          && !force) || !appData.periodicUpdates)
4405       return;
4406
4407     /* Send . command to Crafty to collect stats */
4408     SendToProgram(".\n", &first);
4409
4410     /* Don't send another until we get a response (this makes
4411        us stop sending to old Crafty's which don't understand
4412        the "." command (sending illegal cmds resets node count & time,
4413        which looks bad)) */
4414     programStats.ok_to_send = 0;
4415 }
4416
4417 void ics_update_width(new_width)
4418         int new_width;
4419 {
4420         ics_printf("set width %d\n", new_width);
4421 }
4422
4423 void
4424 SendMoveToProgram(moveNum, cps)
4425      int moveNum;
4426      ChessProgramState *cps;
4427 {
4428     char buf[MSG_SIZ];
4429
4430     if (cps->useUsermove) {
4431       SendToProgram("usermove ", cps);
4432     }
4433     if (cps->useSAN) {
4434       char *space;
4435       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4436         int len = space - parseList[moveNum];
4437         memcpy(buf, parseList[moveNum], len);
4438         buf[len++] = '\n';
4439         buf[len] = NULLCHAR;
4440       } else {
4441         sprintf(buf, "%s\n", parseList[moveNum]);
4442       }
4443       SendToProgram(buf, cps);
4444     } else {
4445       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4446         AlphaRank(moveList[moveNum], 4);
4447         SendToProgram(moveList[moveNum], cps);
4448         AlphaRank(moveList[moveNum], 4); // and back
4449       } else
4450       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4451        * the engine. It would be nice to have a better way to identify castle 
4452        * moves here. */
4453       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4454                                                                          && cps->useOOCastle) {
4455         int fromX = moveList[moveNum][0] - AAA; 
4456         int fromY = moveList[moveNum][1] - ONE;
4457         int toX = moveList[moveNum][2] - AAA; 
4458         int toY = moveList[moveNum][3] - ONE;
4459         if((boards[moveNum][fromY][fromX] == WhiteKing 
4460             && boards[moveNum][toY][toX] == WhiteRook)
4461            || (boards[moveNum][fromY][fromX] == BlackKing 
4462                && boards[moveNum][toY][toX] == BlackRook)) {
4463           if(toX > fromX) SendToProgram("O-O\n", cps);
4464           else SendToProgram("O-O-O\n", cps);
4465         }
4466         else SendToProgram(moveList[moveNum], cps);
4467       }
4468       else SendToProgram(moveList[moveNum], cps);
4469       /* End of additions by Tord */
4470     }
4471
4472     /* [HGM] setting up the opening has brought engine in force mode! */
4473     /*       Send 'go' if we are in a mode where machine should play. */
4474     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4475         (gameMode == TwoMachinesPlay   ||
4476 #ifdef ZIPPY
4477          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4478 #endif
4479          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4480         SendToProgram("go\n", cps);
4481   if (appData.debugMode) {
4482     fprintf(debugFP, "(extra)\n");
4483   }
4484     }
4485     setboardSpoiledMachineBlack = 0;
4486 }
4487
4488 void
4489 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4490      ChessMove moveType;
4491      int fromX, fromY, toX, toY;
4492 {
4493     char user_move[MSG_SIZ];
4494
4495     switch (moveType) {
4496       default:
4497         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4498                 (int)moveType, fromX, fromY, toX, toY);
4499         DisplayError(user_move + strlen("say "), 0);
4500         break;
4501       case WhiteKingSideCastle:
4502       case BlackKingSideCastle:
4503       case WhiteQueenSideCastleWild:
4504       case BlackQueenSideCastleWild:
4505       /* PUSH Fabien */
4506       case WhiteHSideCastleFR:
4507       case BlackHSideCastleFR:
4508       /* POP Fabien */
4509         sprintf(user_move, "o-o\n");
4510         break;
4511       case WhiteQueenSideCastle:
4512       case BlackQueenSideCastle:
4513       case WhiteKingSideCastleWild:
4514       case BlackKingSideCastleWild:
4515       /* PUSH Fabien */
4516       case WhiteASideCastleFR:
4517       case BlackASideCastleFR:
4518       /* POP Fabien */
4519         sprintf(user_move, "o-o-o\n");
4520         break;
4521       case WhitePromotionQueen:
4522       case BlackPromotionQueen:
4523       case WhitePromotionRook:
4524       case BlackPromotionRook:
4525       case WhitePromotionBishop:
4526       case BlackPromotionBishop:
4527       case WhitePromotionKnight:
4528       case BlackPromotionKnight:
4529       case WhitePromotionKing:
4530       case BlackPromotionKing:
4531       case WhitePromotionChancellor:
4532       case BlackPromotionChancellor:
4533       case WhitePromotionArchbishop:
4534       case BlackPromotionArchbishop:
4535         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4536             sprintf(user_move, "%c%c%c%c=%c\n",
4537                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4538                 PieceToChar(WhiteFerz));
4539         else if(gameInfo.variant == VariantGreat)
4540             sprintf(user_move, "%c%c%c%c=%c\n",
4541                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4542                 PieceToChar(WhiteMan));
4543         else
4544             sprintf(user_move, "%c%c%c%c=%c\n",
4545                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4546                 PieceToChar(PromoPiece(moveType)));
4547         break;
4548       case WhiteDrop:
4549       case BlackDrop:
4550         sprintf(user_move, "%c@%c%c\n",
4551                 ToUpper(PieceToChar((ChessSquare) fromX)),
4552                 AAA + toX, ONE + toY);
4553         break;
4554       case NormalMove:
4555       case WhiteCapturesEnPassant:
4556       case BlackCapturesEnPassant:
4557       case IllegalMove:  /* could be a variant we don't quite understand */
4558         sprintf(user_move, "%c%c%c%c\n",
4559                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4560         break;
4561     }
4562     SendToICS(user_move);
4563     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4564         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4565 }
4566
4567 void
4568 UploadGameEvent()
4569 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4570     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4571     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4572     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4573         DisplayError("You cannot do this while you are playing or observing", 0);
4574         return;
4575     }
4576     if(gameMode != IcsExamining) { // is this ever not the case?
4577         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4578
4579         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4580             sprintf(command, "match %s", ics_handle);
4581         } else { // on FICS we must first go to general examine mode
4582             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4583         }
4584         if(gameInfo.variant != VariantNormal) {
4585             // try figure out wild number, as xboard names are not always valid on ICS
4586             for(i=1; i<=36; i++) {
4587                 sprintf(buf, "wild/%d", i);
4588                 if(StringToVariant(buf) == gameInfo.variant) break;
4589             }
4590             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4591             else if(i == 22) sprintf(buf, "%s fr\n", command);
4592             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4593         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4594         SendToICS(ics_prefix);
4595         SendToICS(buf);
4596         if(startedFromSetupPosition || backwardMostMove != 0) {
4597           fen = PositionToFEN(backwardMostMove, NULL);
4598           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4599             sprintf(buf, "loadfen %s\n", fen);
4600             SendToICS(buf);
4601           } else { // FICS: everything has to set by separate bsetup commands
4602             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4603             sprintf(buf, "bsetup fen %s\n", fen);
4604             SendToICS(buf);
4605             if(!WhiteOnMove(backwardMostMove)) {
4606                 SendToICS("bsetup tomove black\n");
4607             }
4608             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4609             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4610             SendToICS(buf);
4611             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4612             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4613             SendToICS(buf);
4614             i = boards[backwardMostMove][EP_STATUS];
4615             if(i >= 0) { // set e.p.
4616                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4617                 SendToICS(buf);
4618             }
4619             bsetup++;
4620           }
4621         }
4622       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4623             SendToICS("bsetup done\n"); // switch to normal examining.
4624     }
4625     for(i = backwardMostMove; i<last; i++) {
4626         char buf[20];
4627         sprintf(buf, "%s\n", parseList[i]);
4628         SendToICS(buf);
4629     }
4630     SendToICS(ics_prefix);
4631     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4632 }
4633
4634 void
4635 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4636      int rf, ff, rt, ft;
4637      char promoChar;
4638      char move[7];
4639 {
4640     if (rf == DROP_RANK) {
4641         sprintf(move, "%c@%c%c\n",
4642                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4643     } else {
4644         if (promoChar == 'x' || promoChar == NULLCHAR) {
4645             sprintf(move, "%c%c%c%c\n",
4646                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4647         } else {
4648             sprintf(move, "%c%c%c%c%c\n",
4649                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4650         }
4651     }
4652 }
4653
4654 void
4655 ProcessICSInitScript(f)
4656      FILE *f;
4657 {
4658     char buf[MSG_SIZ];
4659
4660     while (fgets(buf, MSG_SIZ, f)) {
4661         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4662     }
4663
4664     fclose(f);
4665 }
4666
4667
4668 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4669 void
4670 AlphaRank(char *move, int n)
4671 {
4672 //    char *p = move, c; int x, y;
4673
4674     if (appData.debugMode) {
4675         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4676     }
4677
4678     if(move[1]=='*' && 
4679        move[2]>='0' && move[2]<='9' &&
4680        move[3]>='a' && move[3]<='x'    ) {
4681         move[1] = '@';
4682         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4683         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4684     } else
4685     if(move[0]>='0' && move[0]<='9' &&
4686        move[1]>='a' && move[1]<='x' &&
4687        move[2]>='0' && move[2]<='9' &&
4688        move[3]>='a' && move[3]<='x'    ) {
4689         /* input move, Shogi -> normal */
4690         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4691         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4692         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4693         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4694     } else
4695     if(move[1]=='@' &&
4696        move[3]>='0' && move[3]<='9' &&
4697        move[2]>='a' && move[2]<='x'    ) {
4698         move[1] = '*';
4699         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4700         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4701     } else
4702     if(
4703        move[0]>='a' && move[0]<='x' &&
4704        move[3]>='0' && move[3]<='9' &&
4705        move[2]>='a' && move[2]<='x'    ) {
4706          /* output move, normal -> Shogi */
4707         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4708         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4709         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4710         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4711         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4712     }
4713     if (appData.debugMode) {
4714         fprintf(debugFP, "   out = '%s'\n", move);
4715     }
4716 }
4717
4718 /* Parser for moves from gnuchess, ICS, or user typein box */
4719 Boolean
4720 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4721      char *move;
4722      int moveNum;
4723      ChessMove *moveType;
4724      int *fromX, *fromY, *toX, *toY;
4725      char *promoChar;
4726 {       
4727     if (appData.debugMode) {
4728         fprintf(debugFP, "move to parse: %s\n", move);
4729     }
4730     *moveType = yylexstr(moveNum, move);
4731
4732     switch (*moveType) {
4733       case WhitePromotionChancellor:
4734       case BlackPromotionChancellor:
4735       case WhitePromotionArchbishop:
4736       case BlackPromotionArchbishop:
4737       case WhitePromotionQueen:
4738       case BlackPromotionQueen:
4739       case WhitePromotionRook:
4740       case BlackPromotionRook:
4741       case WhitePromotionBishop:
4742       case BlackPromotionBishop:
4743       case WhitePromotionKnight:
4744       case BlackPromotionKnight:
4745       case WhitePromotionKing:
4746       case BlackPromotionKing:
4747       case NormalMove:
4748       case WhiteCapturesEnPassant:
4749       case BlackCapturesEnPassant:
4750       case WhiteKingSideCastle:
4751       case WhiteQueenSideCastle:
4752       case BlackKingSideCastle:
4753       case BlackQueenSideCastle:
4754       case WhiteKingSideCastleWild:
4755       case WhiteQueenSideCastleWild:
4756       case BlackKingSideCastleWild:
4757       case BlackQueenSideCastleWild:
4758       /* Code added by Tord: */
4759       case WhiteHSideCastleFR:
4760       case WhiteASideCastleFR:
4761       case BlackHSideCastleFR:
4762       case BlackASideCastleFR:
4763       /* End of code added by Tord */
4764       case IllegalMove:         /* bug or odd chess variant */
4765         *fromX = currentMoveString[0] - AAA;
4766         *fromY = currentMoveString[1] - ONE;
4767         *toX = currentMoveString[2] - AAA;
4768         *toY = currentMoveString[3] - ONE;
4769         *promoChar = currentMoveString[4];
4770         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4771             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4772     if (appData.debugMode) {
4773         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4774     }
4775             *fromX = *fromY = *toX = *toY = 0;
4776             return FALSE;
4777         }
4778         if (appData.testLegality) {
4779           return (*moveType != IllegalMove);
4780         } else {
4781           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4782                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4783         }
4784
4785       case WhiteDrop:
4786       case BlackDrop:
4787         *fromX = *moveType == WhiteDrop ?
4788           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4789           (int) CharToPiece(ToLower(currentMoveString[0]));
4790         *fromY = DROP_RANK;
4791         *toX = currentMoveString[2] - AAA;
4792         *toY = currentMoveString[3] - ONE;
4793         *promoChar = NULLCHAR;
4794         return TRUE;
4795
4796       case AmbiguousMove:
4797       case ImpossibleMove:
4798       case (ChessMove) 0:       /* end of file */
4799       case ElapsedTime:
4800       case Comment:
4801       case PGNTag:
4802       case NAG:
4803       case WhiteWins:
4804       case BlackWins:
4805       case GameIsDrawn:
4806       default:
4807     if (appData.debugMode) {
4808         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4809     }
4810         /* bug? */
4811         *fromX = *fromY = *toX = *toY = 0;
4812         *promoChar = NULLCHAR;
4813         return FALSE;
4814     }
4815 }
4816
4817
4818 void
4819 ParsePV(char *pv, Boolean storeComments)
4820 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4821   int fromX, fromY, toX, toY; char promoChar;
4822   ChessMove moveType;
4823   Boolean valid;
4824   int nr = 0, dummy;
4825
4826   endPV = forwardMostMove;
4827   do {
4828     while(*pv == ' ') pv++;
4829     if(nr == 0 && *pv == '(') pv++; // first (ponder) move can be in parentheses
4830     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4831 if(appData.debugMode){
4832 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4833 }
4834     if(!valid && nr == 0 &&
4835        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4836         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4837         // Hande case where played move is different from leading PV move
4838         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4839         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4840         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4841         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4842           endPV += 2; // if position different, keep this
4843           moveList[endPV-1][0] = fromX + AAA;
4844           moveList[endPV-1][1] = fromY + ONE;
4845           moveList[endPV-1][2] = toX + AAA;
4846           moveList[endPV-1][3] = toY + ONE;
4847           parseList[endPV-1][0] = NULLCHAR;
4848           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4849         }
4850       }
4851     if(moveType == Comment) {
4852         // [HGM] vari: try to skip comment
4853         int level = 0; char c, *start = pv, wait = NULLCHAR;
4854         do {
4855             if(!wait) {
4856                 if(*pv == '(') level++; else
4857                 if(*pv == ')' && level) level--;
4858             }
4859             if(*pv == wait) wait = NULLCHAR; else
4860             if(*pv == '{') wait = '}'; else
4861             if(*pv == '[') wait = ']';
4862             pv++;
4863         } while(*pv && (wait || level));
4864         if(storeComments) {
4865           c = *pv; *pv = NULLCHAR;
4866           AppendComment(endPV, start, FALSE);
4867           *pv = c;
4868         }
4869       valid++; // allow comments in PV
4870         continue;
4871     }
4872     if(sscanf(pv, "%d...", &dummy) == 1 || sscanf(pv, "%d.", &dummy) == 1)
4873         while(*pv && *pv++ != ' '); // skip any move numbers
4874     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4875     nr++;
4876     if(endPV+1 > framePtr) break; // no space, truncate
4877     if(!valid) break;
4878     endPV++;
4879     CopyBoard(boards[endPV], boards[endPV-1]);
4880     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4881     moveList[endPV-1][0] = fromX + AAA;
4882     moveList[endPV-1][1] = fromY + ONE;
4883     moveList[endPV-1][2] = toX + AAA;
4884     moveList[endPV-1][3] = toY + ONE;
4885     if(storeComments)
4886         CoordsToAlgebraic(boards[endPV - 1],
4887                              PosFlags(endPV - 1),
4888                              fromY, fromX, toY, toX, promoChar,
4889                              parseList[endPV - 1]);
4890     else
4891         parseList[endPV-1][0] = NULLCHAR;
4892   } while(valid);
4893   currentMove = endPV;
4894   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4895   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4896                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4897   DrawPosition(TRUE, boards[currentMove]);
4898 }
4899
4900 static int lastX, lastY;
4901
4902 Boolean
4903 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4904 {
4905         int startPV;
4906
4907         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4908         lastX = x; lastY = y;
4909         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4910         startPV = index;
4911       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4912       index = startPV;
4913         while(buf[index] && buf[index] != '\n') index++;
4914         buf[index] = 0;
4915         ParsePV(buf+startPV, FALSE);
4916         *start = startPV; *end = index-1;
4917         return TRUE;
4918 }
4919
4920 Boolean
4921 LoadPV(int x, int y)
4922 { // called on right mouse click to load PV
4923   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4924   lastX = x; lastY = y;
4925   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4926   return TRUE;
4927 }
4928
4929 void
4930 UnLoadPV()
4931 {
4932   if(endPV < 0) return;
4933   endPV = -1;
4934   currentMove = forwardMostMove;
4935   ClearPremoveHighlights();
4936   DrawPosition(TRUE, boards[currentMove]);
4937 }
4938
4939 void
4940 MovePV(int x, int y, int h)
4941 { // step through PV based on mouse coordinates (called on mouse move)
4942   int margin = h>>3, step = 0;
4943
4944   if(endPV < 0) return;
4945   // we must somehow check if right button is still down (might be released off board!)
4946   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4947   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4948   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4949   if(!step) return;
4950   lastX = x; lastY = y;
4951   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4952   currentMove += step;
4953   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4954   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4955                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4956   DrawPosition(FALSE, boards[currentMove]);
4957 }
4958
4959
4960 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4961 // All positions will have equal probability, but the current method will not provide a unique
4962 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4963 #define DARK 1
4964 #define LITE 2
4965 #define ANY 3
4966
4967 int squaresLeft[4];
4968 int piecesLeft[(int)BlackPawn];
4969 int seed, nrOfShuffles;
4970
4971 void GetPositionNumber()
4972 {       // sets global variable seed
4973         int i;
4974
4975         seed = appData.defaultFrcPosition;
4976         if(seed < 0) { // randomize based on time for negative FRC position numbers
4977                 for(i=0; i<50; i++) seed += random();
4978                 seed = random() ^ random() >> 8 ^ random() << 8;
4979                 if(seed<0) seed = -seed;
4980         }
4981 }
4982
4983 int put(Board board, int pieceType, int rank, int n, int shade)
4984 // put the piece on the (n-1)-th empty squares of the given shade
4985 {
4986         int i;
4987
4988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4989                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4990                         board[rank][i] = (ChessSquare) pieceType;
4991                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4992                         squaresLeft[ANY]--;
4993                         piecesLeft[pieceType]--; 
4994                         return i;
4995                 }
4996         }
4997         return -1;
4998 }
4999
5000
5001 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5002 // calculate where the next piece goes, (any empty square), and put it there
5003 {
5004         int i;
5005
5006         i = seed % squaresLeft[shade];
5007         nrOfShuffles *= squaresLeft[shade];
5008         seed /= squaresLeft[shade];
5009         put(board, pieceType, rank, i, shade);
5010 }
5011
5012 void AddTwoPieces(Board board, int pieceType, int rank)
5013 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5014 {
5015         int i, n=squaresLeft[ANY], j=n-1, k;
5016
5017         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5018         i = seed % k;  // pick one
5019         nrOfShuffles *= k;
5020         seed /= k;
5021         while(i >= j) i -= j--;
5022         j = n - 1 - j; i += j;
5023         put(board, pieceType, rank, j, ANY);
5024         put(board, pieceType, rank, i, ANY);
5025 }
5026
5027 void SetUpShuffle(Board board, int number)
5028 {
5029         int i, p, first=1;
5030
5031         GetPositionNumber(); nrOfShuffles = 1;
5032
5033         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5034         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5035         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5036
5037         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5038
5039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5040             p = (int) board[0][i];
5041             if(p < (int) BlackPawn) piecesLeft[p] ++;
5042             board[0][i] = EmptySquare;
5043         }
5044
5045         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5046             // shuffles restricted to allow normal castling put KRR first
5047             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5048                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5049             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5050                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5051             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5052                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5053             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5054                 put(board, WhiteRook, 0, 0, ANY);
5055             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5056         }
5057
5058         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5059             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5060             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5061                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5062                 while(piecesLeft[p] >= 2) {
5063                     AddOnePiece(board, p, 0, LITE);
5064                     AddOnePiece(board, p, 0, DARK);
5065                 }
5066                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5067             }
5068
5069         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5070             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5071             // but we leave King and Rooks for last, to possibly obey FRC restriction
5072             if(p == (int)WhiteRook) continue;
5073             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5074             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5075         }
5076
5077         // now everything is placed, except perhaps King (Unicorn) and Rooks
5078
5079         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5080             // Last King gets castling rights
5081             while(piecesLeft[(int)WhiteUnicorn]) {
5082                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5083                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5084             }
5085
5086             while(piecesLeft[(int)WhiteKing]) {
5087                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5088                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5089             }
5090
5091
5092         } else {
5093             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5094             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5095         }
5096
5097         // Only Rooks can be left; simply place them all
5098         while(piecesLeft[(int)WhiteRook]) {
5099                 i = put(board, WhiteRook, 0, 0, ANY);
5100                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5101                         if(first) {
5102                                 first=0;
5103                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5104                         }
5105                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5106                 }
5107         }
5108         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5109             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5110         }
5111
5112         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5113 }
5114
5115 int SetCharTable( char *table, const char * map )
5116 /* [HGM] moved here from winboard.c because of its general usefulness */
5117 /*       Basically a safe strcpy that uses the last character as King */
5118 {
5119     int result = FALSE; int NrPieces;
5120
5121     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5122                     && NrPieces >= 12 && !(NrPieces&1)) {
5123         int i; /* [HGM] Accept even length from 12 to 34 */
5124
5125         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5126         for( i=0; i<NrPieces/2-1; i++ ) {
5127             table[i] = map[i];
5128             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5129         }
5130         table[(int) WhiteKing]  = map[NrPieces/2-1];
5131         table[(int) BlackKing]  = map[NrPieces-1];
5132
5133         result = TRUE;
5134     }
5135
5136     return result;
5137 }
5138
5139 void Prelude(Board board)
5140 {       // [HGM] superchess: random selection of exo-pieces
5141         int i, j, k; ChessSquare p; 
5142         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5143
5144         GetPositionNumber(); // use FRC position number
5145
5146         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5147             SetCharTable(pieceToChar, appData.pieceToCharTable);
5148             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5149                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5150         }
5151
5152         j = seed%4;                 seed /= 4; 
5153         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5154         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5155         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5156         j = seed%3 + (seed%3 >= j); seed /= 3; 
5157         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5158         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5159         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5160         j = seed%3;                 seed /= 3; 
5161         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5162         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5163         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5164         j = seed%2 + (seed%2 >= j); seed /= 2; 
5165         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5166         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5167         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5168         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5169         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5170         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5171         put(board, exoPieces[0],    0, 0, ANY);
5172         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5173 }
5174
5175 void
5176 InitPosition(redraw)
5177      int redraw;
5178 {
5179     ChessSquare (* pieces)[BOARD_FILES];
5180     int i, j, pawnRow, overrule,
5181     oldx = gameInfo.boardWidth,
5182     oldy = gameInfo.boardHeight,
5183     oldh = gameInfo.holdingsWidth,
5184     oldv = gameInfo.variant;
5185
5186     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5187
5188     /* [AS] Initialize pv info list [HGM] and game status */
5189     {
5190         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5191             pvInfoList[i].depth = 0;
5192             boards[i][EP_STATUS] = EP_NONE;
5193             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5194         }
5195
5196         initialRulePlies = 0; /* 50-move counter start */
5197
5198         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5199         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5200     }
5201
5202     
5203     /* [HGM] logic here is completely changed. In stead of full positions */
5204     /* the initialized data only consist of the two backranks. The switch */
5205     /* selects which one we will use, which is than copied to the Board   */
5206     /* initialPosition, which for the rest is initialized by Pawns and    */
5207     /* empty squares. This initial position is then copied to boards[0],  */
5208     /* possibly after shuffling, so that it remains available.            */
5209
5210     gameInfo.holdingsWidth = 0; /* default board sizes */
5211     gameInfo.boardWidth    = 8;
5212     gameInfo.boardHeight   = 8;
5213     gameInfo.holdingsSize  = 0;
5214     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5215     for(i=0; i<BOARD_FILES-2; i++)
5216       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5217     initialPosition[EP_STATUS] = EP_NONE;
5218     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5219
5220     switch (gameInfo.variant) {
5221     case VariantFischeRandom:
5222       shuffleOpenings = TRUE;
5223     default:
5224       pieces = FIDEArray;
5225       break;
5226     case VariantShatranj:
5227       pieces = ShatranjArray;
5228       nrCastlingRights = 0;
5229       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5230       break;
5231     case VariantMakruk:
5232       pieces = makrukArray;
5233       nrCastlingRights = 0;
5234       startedFromSetupPosition = TRUE;
5235       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5236       break;
5237     case VariantTwoKings:
5238       pieces = twoKingsArray;
5239       break;
5240     case VariantCapaRandom:
5241       shuffleOpenings = TRUE;
5242     case VariantCapablanca:
5243       pieces = CapablancaArray;
5244       gameInfo.boardWidth = 10;
5245       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5246       break;
5247     case VariantGothic:
5248       pieces = GothicArray;
5249       gameInfo.boardWidth = 10;
5250       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5251       break;
5252     case VariantJanus:
5253       pieces = JanusArray;
5254       gameInfo.boardWidth = 10;
5255       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5256       nrCastlingRights = 6;
5257         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5258         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5259         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5260         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5261         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5262         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5263       break;
5264     case VariantFalcon:
5265       pieces = FalconArray;
5266       gameInfo.boardWidth = 10;
5267       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5268       break;
5269     case VariantXiangqi:
5270       pieces = XiangqiArray;
5271       gameInfo.boardWidth  = 9;
5272       gameInfo.boardHeight = 10;
5273       nrCastlingRights = 0;
5274       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5275       break;
5276     case VariantShogi:
5277       pieces = ShogiArray;
5278       gameInfo.boardWidth  = 9;
5279       gameInfo.boardHeight = 9;
5280       gameInfo.holdingsSize = 7;
5281       nrCastlingRights = 0;
5282       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5283       break;
5284     case VariantCourier:
5285       pieces = CourierArray;
5286       gameInfo.boardWidth  = 12;
5287       nrCastlingRights = 0;
5288       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5289       break;
5290     case VariantKnightmate:
5291       pieces = KnightmateArray;
5292       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5293       break;
5294     case VariantFairy:
5295       pieces = fairyArray;
5296       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5297       break;
5298     case VariantGreat:
5299       pieces = GreatArray;
5300       gameInfo.boardWidth = 10;
5301       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5302       gameInfo.holdingsSize = 8;
5303       break;
5304     case VariantSuper:
5305       pieces = FIDEArray;
5306       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5307       gameInfo.holdingsSize = 8;
5308       startedFromSetupPosition = TRUE;
5309       break;
5310     case VariantCrazyhouse:
5311     case VariantBughouse:
5312       pieces = FIDEArray;
5313       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5314       gameInfo.holdingsSize = 5;
5315       break;
5316     case VariantWildCastle:
5317       pieces = FIDEArray;
5318       /* !!?shuffle with kings guaranteed to be on d or e file */
5319       shuffleOpenings = 1;
5320       break;
5321     case VariantNoCastle:
5322       pieces = FIDEArray;
5323       nrCastlingRights = 0;
5324       /* !!?unconstrained back-rank shuffle */
5325       shuffleOpenings = 1;
5326       break;
5327     }
5328
5329     overrule = 0;
5330     if(appData.NrFiles >= 0) {
5331         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5332         gameInfo.boardWidth = appData.NrFiles;
5333     }
5334     if(appData.NrRanks >= 0) {
5335         gameInfo.boardHeight = appData.NrRanks;
5336     }
5337     if(appData.holdingsSize >= 0) {
5338         i = appData.holdingsSize;
5339         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5340         gameInfo.holdingsSize = i;
5341     }
5342     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5343     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5344         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5345
5346     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5347     if(pawnRow < 1) pawnRow = 1;
5348     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5349
5350     /* User pieceToChar list overrules defaults */
5351     if(appData.pieceToCharTable != NULL)
5352         SetCharTable(pieceToChar, appData.pieceToCharTable);
5353
5354     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5355
5356         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5357             s = (ChessSquare) 0; /* account holding counts in guard band */
5358         for( i=0; i<BOARD_HEIGHT; i++ )
5359             initialPosition[i][j] = s;
5360
5361         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5362         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5363         initialPosition[pawnRow][j] = WhitePawn;
5364         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5365         if(gameInfo.variant == VariantXiangqi) {
5366             if(j&1) {
5367                 initialPosition[pawnRow][j] = 
5368                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5369                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5370                    initialPosition[2][j] = WhiteCannon;
5371                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5372                 }
5373             }
5374         }
5375         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5376     }
5377     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5378
5379             j=BOARD_LEFT+1;
5380             initialPosition[1][j] = WhiteBishop;
5381             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5382             j=BOARD_RGHT-2;
5383             initialPosition[1][j] = WhiteRook;
5384             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5385     }
5386
5387     if( nrCastlingRights == -1) {
5388         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5389         /*       This sets default castling rights from none to normal corners   */
5390         /* Variants with other castling rights must set them themselves above    */
5391         nrCastlingRights = 6;
5392        
5393         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5394         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5395         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5396         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5398         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5399      }
5400
5401      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5402      if(gameInfo.variant == VariantGreat) { // promotion commoners
5403         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5404         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5405         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5406         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5407      }
5408   if (appData.debugMode) {
5409     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5410   }
5411     if(shuffleOpenings) {
5412         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5413         startedFromSetupPosition = TRUE;
5414     }
5415     if(startedFromPositionFile) {
5416       /* [HGM] loadPos: use PositionFile for every new game */
5417       CopyBoard(initialPosition, filePosition);
5418       for(i=0; i<nrCastlingRights; i++)
5419           initialRights[i] = filePosition[CASTLING][i];
5420       startedFromSetupPosition = TRUE;
5421     }
5422
5423     CopyBoard(boards[0], initialPosition);
5424
5425     if(oldx != gameInfo.boardWidth ||
5426        oldy != gameInfo.boardHeight ||
5427        oldh != gameInfo.holdingsWidth
5428 #ifdef GOTHIC
5429        || oldv == VariantGothic ||        // For licensing popups
5430        gameInfo.variant == VariantGothic
5431 #endif
5432 #ifdef FALCON
5433        || oldv == VariantFalcon ||
5434        gameInfo.variant == VariantFalcon
5435 #endif
5436                                          )
5437             InitDrawingSizes(-2 ,0);
5438
5439     if (redraw)
5440       DrawPosition(TRUE, boards[currentMove]);
5441 }
5442
5443 void
5444 SendBoard(cps, moveNum)
5445      ChessProgramState *cps;
5446      int moveNum;
5447 {
5448     char message[MSG_SIZ];
5449     
5450     if (cps->useSetboard) {
5451       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5452       sprintf(message, "setboard %s\n", fen);
5453       SendToProgram(message, cps);
5454       free(fen);
5455
5456     } else {
5457       ChessSquare *bp;
5458       int i, j;
5459       /* Kludge to set black to move, avoiding the troublesome and now
5460        * deprecated "black" command.
5461        */
5462       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5463
5464       SendToProgram("edit\n", cps);
5465       SendToProgram("#\n", cps);
5466       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5467         bp = &boards[moveNum][i][BOARD_LEFT];
5468         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5469           if ((int) *bp < (int) BlackPawn) {
5470             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5471                     AAA + j, ONE + i);
5472             if(message[0] == '+' || message[0] == '~') {
5473                 sprintf(message, "%c%c%c+\n",
5474                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5475                         AAA + j, ONE + i);
5476             }
5477             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5478                 message[1] = BOARD_RGHT   - 1 - j + '1';
5479                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5480             }
5481             SendToProgram(message, cps);
5482           }
5483         }
5484       }
5485     
5486       SendToProgram("c\n", cps);
5487       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5488         bp = &boards[moveNum][i][BOARD_LEFT];
5489         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5490           if (((int) *bp != (int) EmptySquare)
5491               && ((int) *bp >= (int) BlackPawn)) {
5492             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5493                     AAA + j, ONE + i);
5494             if(message[0] == '+' || message[0] == '~') {
5495                 sprintf(message, "%c%c%c+\n",
5496                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5497                         AAA + j, ONE + i);
5498             }
5499             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5500                 message[1] = BOARD_RGHT   - 1 - j + '1';
5501                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5502             }
5503             SendToProgram(message, cps);
5504           }
5505         }
5506       }
5507     
5508       SendToProgram(".\n", cps);
5509     }
5510     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5511 }
5512
5513 static int autoQueen; // [HGM] oneclick
5514
5515 int
5516 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5517 {
5518     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5519     /* [HGM] add Shogi promotions */
5520     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5521     ChessSquare piece;
5522     ChessMove moveType;
5523     Boolean premove;
5524
5525     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5526     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5527
5528     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5529       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5530         return FALSE;
5531
5532     piece = boards[currentMove][fromY][fromX];
5533     if(gameInfo.variant == VariantShogi) {
5534         promotionZoneSize = 3;
5535         highestPromotingPiece = (int)WhiteFerz;
5536     } else if(gameInfo.variant == VariantMakruk) {
5537         promotionZoneSize = 3;
5538     }
5539
5540     // next weed out all moves that do not touch the promotion zone at all
5541     if((int)piece >= BlackPawn) {
5542         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5543              return FALSE;
5544         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5545     } else {
5546         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5547            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5548     }
5549
5550     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5551
5552     // weed out mandatory Shogi promotions
5553     if(gameInfo.variant == VariantShogi) {
5554         if(piece >= BlackPawn) {
5555             if(toY == 0 && piece == BlackPawn ||
5556                toY == 0 && piece == BlackQueen ||
5557                toY <= 1 && piece == BlackKnight) {
5558                 *promoChoice = '+';
5559                 return FALSE;
5560             }
5561         } else {
5562             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5563                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5564                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5565                 *promoChoice = '+';
5566                 return FALSE;
5567             }
5568         }
5569     }
5570
5571     // weed out obviously illegal Pawn moves
5572     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5573         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5574         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5575         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5576         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5577         // note we are not allowed to test for valid (non-)capture, due to premove
5578     }
5579
5580     // we either have a choice what to promote to, or (in Shogi) whether to promote
5581     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5582         *promoChoice = PieceToChar(BlackFerz);  // no choice
5583         return FALSE;
5584     }
5585     if(autoQueen) { // predetermined
5586         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5587              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5588         else *promoChoice = PieceToChar(BlackQueen);
5589         return FALSE;
5590     }
5591
5592     // suppress promotion popup on illegal moves that are not premoves
5593     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5594               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5595     if(appData.testLegality && !premove) {
5596         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5597                         fromY, fromX, toY, toX, NULLCHAR);
5598         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5599            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5600             return FALSE;
5601     }
5602
5603     return TRUE;
5604 }
5605
5606 int
5607 InPalace(row, column)
5608      int row, column;
5609 {   /* [HGM] for Xiangqi */
5610     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5611          column < (BOARD_WIDTH + 4)/2 &&
5612          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5613     return FALSE;
5614 }
5615
5616 int
5617 PieceForSquare (x, y)
5618      int x;
5619      int y;
5620 {
5621   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5622      return -1;
5623   else
5624      return boards[currentMove][y][x];
5625 }
5626
5627 int
5628 OKToStartUserMove(x, y)
5629      int x, y;
5630 {
5631     ChessSquare from_piece;
5632     int white_piece;
5633
5634     if (matchMode) return FALSE;
5635     if (gameMode == EditPosition) return TRUE;
5636
5637     if (x >= 0 && y >= 0)
5638       from_piece = boards[currentMove][y][x];
5639     else
5640       from_piece = EmptySquare;
5641
5642     if (from_piece == EmptySquare) return FALSE;
5643
5644     white_piece = (int)from_piece >= (int)WhitePawn &&
5645       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5646
5647     switch (gameMode) {
5648       case PlayFromGameFile:
5649       case AnalyzeFile:
5650       case TwoMachinesPlay:
5651       case EndOfGame:
5652         return FALSE;
5653
5654       case IcsObserving:
5655       case IcsIdle:
5656         return FALSE;
5657
5658       case MachinePlaysWhite:
5659       case IcsPlayingBlack:
5660         if (appData.zippyPlay) return FALSE;
5661         if (white_piece) {
5662             DisplayMoveError(_("You are playing Black"));
5663             return FALSE;
5664         }
5665         break;
5666
5667       case MachinePlaysBlack:
5668       case IcsPlayingWhite:
5669         if (appData.zippyPlay) return FALSE;
5670         if (!white_piece) {
5671             DisplayMoveError(_("You are playing White"));
5672             return FALSE;
5673         }
5674         break;
5675
5676       case EditGame:
5677         if (!white_piece && WhiteOnMove(currentMove)) {
5678             DisplayMoveError(_("It is White's turn"));
5679             return FALSE;
5680         }           
5681         if (white_piece && !WhiteOnMove(currentMove)) {
5682             DisplayMoveError(_("It is Black's turn"));
5683             return FALSE;
5684         }           
5685         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5686             /* Editing correspondence game history */
5687             /* Could disallow this or prompt for confirmation */
5688             cmailOldMove = -1;
5689         }
5690         break;
5691
5692       case BeginningOfGame:
5693         if (appData.icsActive) return FALSE;
5694         if (!appData.noChessProgram) {
5695             if (!white_piece) {
5696                 DisplayMoveError(_("You are playing White"));
5697                 return FALSE;
5698             }
5699         }
5700         break;
5701         
5702       case Training:
5703         if (!white_piece && WhiteOnMove(currentMove)) {
5704             DisplayMoveError(_("It is White's turn"));
5705             return FALSE;
5706         }           
5707         if (white_piece && !WhiteOnMove(currentMove)) {
5708             DisplayMoveError(_("It is Black's turn"));
5709             return FALSE;
5710         }           
5711         break;
5712
5713       default:
5714       case IcsExamining:
5715         break;
5716     }
5717     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5718         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5719         && gameMode != AnalyzeFile && gameMode != Training) {
5720         DisplayMoveError(_("Displayed position is not current"));
5721         return FALSE;
5722     }
5723     return TRUE;
5724 }
5725
5726 Boolean
5727 OnlyMove(int *x, int *y, Boolean captures) {
5728     DisambiguateClosure cl;
5729     if (appData.zippyPlay) return FALSE;
5730     switch(gameMode) {
5731       case MachinePlaysBlack:
5732       case IcsPlayingWhite:
5733       case BeginningOfGame:
5734         if(!WhiteOnMove(currentMove)) return FALSE;
5735         break;
5736       case MachinePlaysWhite:
5737       case IcsPlayingBlack:
5738         if(WhiteOnMove(currentMove)) return FALSE;
5739         break;
5740       default:
5741         return FALSE;
5742     }
5743     cl.pieceIn = EmptySquare; 
5744     cl.rfIn = *y;
5745     cl.ffIn = *x;
5746     cl.rtIn = -1;
5747     cl.ftIn = -1;
5748     cl.promoCharIn = NULLCHAR;
5749     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5750     if( cl.kind == NormalMove ||
5751         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5752         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5753         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5754         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5755       fromX = cl.ff;
5756       fromY = cl.rf;
5757       *x = cl.ft;
5758       *y = cl.rt;
5759       return TRUE;
5760     }
5761     if(cl.kind != ImpossibleMove) return FALSE;
5762     cl.pieceIn = EmptySquare;
5763     cl.rfIn = -1;
5764     cl.ffIn = -1;
5765     cl.rtIn = *y;
5766     cl.ftIn = *x;
5767     cl.promoCharIn = NULLCHAR;
5768     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5769     if( cl.kind == NormalMove ||
5770         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5771         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5772         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5773         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5774       fromX = cl.ff;
5775       fromY = cl.rf;
5776       *x = cl.ft;
5777       *y = cl.rt;
5778       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5779       return TRUE;
5780     }
5781     return FALSE;
5782 }
5783
5784 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5785 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5786 int lastLoadGameUseList = FALSE;
5787 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5788 ChessMove lastLoadGameStart = (ChessMove) 0;
5789
5790 ChessMove
5791 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5792      int fromX, fromY, toX, toY;
5793      int promoChar;
5794      Boolean captureOwn;
5795 {
5796     ChessMove moveType;
5797     ChessSquare pdown, pup;
5798
5799     /* Check if the user is playing in turn.  This is complicated because we
5800        let the user "pick up" a piece before it is his turn.  So the piece he
5801        tried to pick up may have been captured by the time he puts it down!
5802        Therefore we use the color the user is supposed to be playing in this
5803        test, not the color of the piece that is currently on the starting
5804        square---except in EditGame mode, where the user is playing both
5805        sides; fortunately there the capture race can't happen.  (It can
5806        now happen in IcsExamining mode, but that's just too bad.  The user
5807        will get a somewhat confusing message in that case.)
5808        */
5809
5810     switch (gameMode) {
5811       case PlayFromGameFile:
5812       case AnalyzeFile:
5813       case TwoMachinesPlay:
5814       case EndOfGame:
5815       case IcsObserving:
5816       case IcsIdle:
5817         /* We switched into a game mode where moves are not accepted,
5818            perhaps while the mouse button was down. */
5819         return ImpossibleMove;
5820
5821       case MachinePlaysWhite:
5822         /* User is moving for Black */
5823         if (WhiteOnMove(currentMove)) {
5824             DisplayMoveError(_("It is White's turn"));
5825             return ImpossibleMove;
5826         }
5827         break;
5828
5829       case MachinePlaysBlack:
5830         /* User is moving for White */
5831         if (!WhiteOnMove(currentMove)) {
5832             DisplayMoveError(_("It is Black's turn"));
5833             return ImpossibleMove;
5834         }
5835         break;
5836
5837       case EditGame:
5838       case IcsExamining:
5839       case BeginningOfGame:
5840       case AnalyzeMode:
5841       case Training:
5842         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5843             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5844             /* User is moving for Black */
5845             if (WhiteOnMove(currentMove)) {
5846                 DisplayMoveError(_("It is White's turn"));
5847                 return ImpossibleMove;
5848             }
5849         } else {
5850             /* User is moving for White */
5851             if (!WhiteOnMove(currentMove)) {
5852                 DisplayMoveError(_("It is Black's turn"));
5853                 return ImpossibleMove;
5854             }
5855         }
5856         break;
5857
5858       case IcsPlayingBlack:
5859         /* User is moving for Black */
5860         if (WhiteOnMove(currentMove)) {
5861             if (!appData.premove) {
5862                 DisplayMoveError(_("It is White's turn"));
5863             } else if (toX >= 0 && toY >= 0) {
5864                 premoveToX = toX;
5865                 premoveToY = toY;
5866                 premoveFromX = fromX;
5867                 premoveFromY = fromY;
5868                 premovePromoChar = promoChar;
5869                 gotPremove = 1;
5870                 if (appData.debugMode) 
5871                     fprintf(debugFP, "Got premove: fromX %d,"
5872                             "fromY %d, toX %d, toY %d\n",
5873                             fromX, fromY, toX, toY);
5874             }
5875             return ImpossibleMove;
5876         }
5877         break;
5878
5879       case IcsPlayingWhite:
5880         /* User is moving for White */
5881         if (!WhiteOnMove(currentMove)) {
5882             if (!appData.premove) {
5883                 DisplayMoveError(_("It is Black's turn"));
5884             } else if (toX >= 0 && toY >= 0) {
5885                 premoveToX = toX;
5886                 premoveToY = toY;
5887                 premoveFromX = fromX;
5888                 premoveFromY = fromY;
5889                 premovePromoChar = promoChar;
5890                 gotPremove = 1;
5891                 if (appData.debugMode) 
5892                     fprintf(debugFP, "Got premove: fromX %d,"
5893                             "fromY %d, toX %d, toY %d\n",
5894                             fromX, fromY, toX, toY);
5895             }
5896             return ImpossibleMove;
5897         }
5898         break;
5899
5900       default:
5901         break;
5902
5903       case EditPosition:
5904         /* EditPosition, empty square, or different color piece;
5905            click-click move is possible */
5906         if (toX == -2 || toY == -2) {
5907             boards[0][fromY][fromX] = EmptySquare;
5908             return AmbiguousMove;
5909         } else if (toX >= 0 && toY >= 0) {
5910             boards[0][toY][toX] = boards[0][fromY][fromX];
5911             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5912                 if(boards[0][fromY][0] != EmptySquare) {
5913                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5914                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5915                 }
5916             } else
5917             if(fromX == BOARD_RGHT+1) {
5918                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5919                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5920                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5921                 }
5922             } else
5923             boards[0][fromY][fromX] = EmptySquare;
5924             return AmbiguousMove;
5925         }
5926         return ImpossibleMove;
5927     }
5928
5929     if(toX < 0 || toY < 0) return ImpossibleMove;
5930     pdown = boards[currentMove][fromY][fromX];
5931     pup = boards[currentMove][toY][toX];
5932
5933     /* [HGM] If move started in holdings, it means a drop */
5934     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5935          if( pup != EmptySquare ) return ImpossibleMove;
5936          if(appData.testLegality) {
5937              /* it would be more logical if LegalityTest() also figured out
5938               * which drops are legal. For now we forbid pawns on back rank.
5939               * Shogi is on its own here...
5940               */
5941              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5942                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5943                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5944          }
5945          return WhiteDrop; /* Not needed to specify white or black yet */
5946     }
5947
5948     /* [HGM] always test for legality, to get promotion info */
5949     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5950                                          fromY, fromX, toY, toX, promoChar);
5951     /* [HGM] but possibly ignore an IllegalMove result */
5952     if (appData.testLegality) {
5953         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5954             DisplayMoveError(_("Illegal move"));
5955             return ImpossibleMove;
5956         }
5957     }
5958
5959     return moveType;
5960     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5961        function is made into one that returns an OK move type if FinishMove
5962        should be called. This to give the calling driver routine the
5963        opportunity to finish the userMove input with a promotion popup,
5964        without bothering the user with this for invalid or illegal moves */
5965
5966 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5967 }
5968
5969 /* Common tail of UserMoveEvent and DropMenuEvent */
5970 int
5971 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5972      ChessMove moveType;
5973      int fromX, fromY, toX, toY;
5974      /*char*/int promoChar;
5975 {
5976     char *bookHit = 0;
5977
5978     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5979         // [HGM] superchess: suppress promotions to non-available piece
5980         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5981         if(WhiteOnMove(currentMove)) {
5982             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5983         } else {
5984             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5985         }
5986     }
5987
5988     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5989        move type in caller when we know the move is a legal promotion */
5990     if(moveType == NormalMove && promoChar)
5991         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5992
5993     /* [HGM] convert drag-and-drop piece drops to standard form */
5994     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5995          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5996            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5997                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5998            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5999            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6000            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6001            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6002          fromY = DROP_RANK;
6003     }
6004
6005     /* [HGM] <popupFix> The following if has been moved here from
6006        UserMoveEvent(). Because it seemed to belong here (why not allow
6007        piece drops in training games?), and because it can only be
6008        performed after it is known to what we promote. */
6009     if (gameMode == Training) {
6010       /* compare the move played on the board to the next move in the
6011        * game. If they match, display the move and the opponent's response. 
6012        * If they don't match, display an error message.
6013        */
6014       int saveAnimate;
6015       Board testBoard;
6016       CopyBoard(testBoard, boards[currentMove]);
6017       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6018
6019       if (CompareBoards(testBoard, boards[currentMove+1])) {
6020         ForwardInner(currentMove+1);
6021
6022         /* Autoplay the opponent's response.
6023          * if appData.animate was TRUE when Training mode was entered,
6024          * the response will be animated.
6025          */
6026         saveAnimate = appData.animate;
6027         appData.animate = animateTraining;
6028         ForwardInner(currentMove+1);
6029         appData.animate = saveAnimate;
6030
6031         /* check for the end of the game */
6032         if (currentMove >= forwardMostMove) {
6033           gameMode = PlayFromGameFile;
6034           ModeHighlight();
6035           SetTrainingModeOff();
6036           DisplayInformation(_("End of game"));
6037         }
6038       } else {
6039         DisplayError(_("Incorrect move"), 0);
6040       }
6041       return 1;
6042     }
6043
6044   /* Ok, now we know that the move is good, so we can kill
6045      the previous line in Analysis Mode */
6046   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6047                                 && currentMove < forwardMostMove) {
6048     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6049   }
6050
6051   /* If we need the chess program but it's dead, restart it */
6052   ResurrectChessProgram();
6053
6054   /* A user move restarts a paused game*/
6055   if (pausing)
6056     PauseEvent();
6057
6058   thinkOutput[0] = NULLCHAR;
6059
6060   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6061
6062   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6063
6064   if (gameMode == BeginningOfGame) {
6065     if (appData.noChessProgram) {
6066       gameMode = EditGame;
6067       SetGameInfo();
6068     } else {
6069       char buf[MSG_SIZ];
6070       gameMode = MachinePlaysBlack;
6071       StartClocks();
6072       SetGameInfo();
6073       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6074       DisplayTitle(buf);
6075       if (first.sendName) {
6076         sprintf(buf, "name %s\n", gameInfo.white);
6077         SendToProgram(buf, &first);
6078       }
6079       StartClocks();
6080     }
6081     ModeHighlight();
6082   }
6083
6084   /* Relay move to ICS or chess engine */
6085   if (appData.icsActive) {
6086     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6087         gameMode == IcsExamining) {
6088       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6089         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6090         SendToICS("draw ");
6091         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6092       }
6093       // also send plain move, in case ICS does not understand atomic claims
6094       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6095       ics_user_moved = 1;
6096     }
6097   } else {
6098     if (first.sendTime && (gameMode == BeginningOfGame ||
6099                            gameMode == MachinePlaysWhite ||
6100                            gameMode == MachinePlaysBlack)) {
6101       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6102     }
6103     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6104          // [HGM] book: if program might be playing, let it use book
6105         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6106         first.maybeThinking = TRUE;
6107     } else SendMoveToProgram(forwardMostMove-1, &first);
6108     if (currentMove == cmailOldMove + 1) {
6109       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6110     }
6111   }
6112
6113   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6114
6115   switch (gameMode) {
6116   case EditGame:
6117     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6118     case MT_NONE:
6119     case MT_CHECK:
6120       break;
6121     case MT_CHECKMATE:
6122     case MT_STAINMATE:
6123       if (WhiteOnMove(currentMove)) {
6124         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6125       } else {
6126         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6127       }
6128       break;
6129     case MT_STALEMATE:
6130       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6131       break;
6132     }
6133     break;
6134     
6135   case MachinePlaysBlack:
6136   case MachinePlaysWhite:
6137     /* disable certain menu options while machine is thinking */
6138     SetMachineThinkingEnables();
6139     break;
6140
6141   default:
6142     break;
6143   }
6144
6145   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6146         
6147   if(bookHit) { // [HGM] book: simulate book reply
6148         static char bookMove[MSG_SIZ]; // a bit generous?
6149
6150         programStats.nodes = programStats.depth = programStats.time = 
6151         programStats.score = programStats.got_only_move = 0;
6152         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6153
6154         strcpy(bookMove, "move ");
6155         strcat(bookMove, bookHit);
6156         HandleMachineMove(bookMove, &first);
6157   }
6158   return 1;
6159 }
6160
6161 void
6162 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6163      int fromX, fromY, toX, toY;
6164      int promoChar;
6165 {
6166     /* [HGM] This routine was added to allow calling of its two logical
6167        parts from other modules in the old way. Before, UserMoveEvent()
6168        automatically called FinishMove() if the move was OK, and returned
6169        otherwise. I separated the two, in order to make it possible to
6170        slip a promotion popup in between. But that it always needs two
6171        calls, to the first part, (now called UserMoveTest() ), and to
6172        FinishMove if the first part succeeded. Calls that do not need
6173        to do anything in between, can call this routine the old way. 
6174     */
6175     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6176 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6177     if(moveType == AmbiguousMove)
6178         DrawPosition(FALSE, boards[currentMove]);
6179     else if(moveType != ImpossibleMove && moveType != Comment)
6180         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6181 }
6182
6183 void
6184 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6185      Board board;
6186      int flags;
6187      ChessMove kind;
6188      int rf, ff, rt, ft;
6189      VOIDSTAR closure;
6190 {
6191     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6192     Markers *m = (Markers *) closure;
6193     if(rf == fromY && ff == fromX)
6194         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6195                          || kind == WhiteCapturesEnPassant
6196                          || kind == BlackCapturesEnPassant);
6197     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6198 }
6199
6200 void
6201 MarkTargetSquares(int clear)
6202 {
6203   int x, y;
6204   if(!appData.markers || !appData.highlightDragging || 
6205      !appData.testLegality || gameMode == EditPosition) return;
6206   if(clear) {
6207     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6208   } else {
6209     int capt = 0;
6210     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6211     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6212       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6213       if(capt)
6214       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6215     }
6216   }
6217   DrawPosition(TRUE, NULL);
6218 }
6219
6220 void LeftClick(ClickType clickType, int xPix, int yPix)
6221 {
6222     int x, y;
6223     Boolean saveAnimate;
6224     static int second = 0, promotionChoice = 0;
6225     char promoChoice = NULLCHAR;
6226
6227     if(appData.seekGraph && appData.icsActive && loggedOn &&
6228         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6229         SeekGraphClick(clickType, xPix, yPix, 0);
6230         return;
6231     }
6232
6233     if (clickType == Press) ErrorPopDown();
6234     MarkTargetSquares(1);
6235
6236     x = EventToSquare(xPix, BOARD_WIDTH);
6237     y = EventToSquare(yPix, BOARD_HEIGHT);
6238     if (!flipView && y >= 0) {
6239         y = BOARD_HEIGHT - 1 - y;
6240     }
6241     if (flipView && x >= 0) {
6242         x = BOARD_WIDTH - 1 - x;
6243     }
6244
6245     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6246         if(clickType == Release) return; // ignore upclick of click-click destination
6247         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6248         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6249         if(gameInfo.holdingsWidth && 
6250                 (WhiteOnMove(currentMove) 
6251                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6252                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6253             // click in right holdings, for determining promotion piece
6254             ChessSquare p = boards[currentMove][y][x];
6255             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6256             if(p != EmptySquare) {
6257                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6258                 fromX = fromY = -1;
6259                 return;
6260             }
6261         }
6262         DrawPosition(FALSE, boards[currentMove]);
6263         return;
6264     }
6265
6266     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6267     if(clickType == Press
6268             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6269               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6270               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6271         return;
6272
6273     autoQueen = appData.alwaysPromoteToQueen;
6274
6275     if (fromX == -1) {
6276       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6277         if (clickType == Press) {
6278             /* First square */
6279             if (OKToStartUserMove(x, y)) {
6280                 fromX = x;
6281                 fromY = y;
6282                 second = 0;
6283                 MarkTargetSquares(0);
6284                 DragPieceBegin(xPix, yPix);
6285                 if (appData.highlightDragging) {
6286                     SetHighlights(x, y, -1, -1);
6287                 }
6288             }
6289         }
6290         return;
6291       }
6292     }
6293
6294     /* fromX != -1 */
6295     if (clickType == Press && gameMode != EditPosition) {
6296         ChessSquare fromP;
6297         ChessSquare toP;
6298         int frc;
6299
6300         // ignore off-board to clicks
6301         if(y < 0 || x < 0) return;
6302
6303         /* Check if clicking again on the same color piece */
6304         fromP = boards[currentMove][fromY][fromX];
6305         toP = boards[currentMove][y][x];
6306         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6307         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6308              WhitePawn <= toP && toP <= WhiteKing &&
6309              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6310              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6311             (BlackPawn <= fromP && fromP <= BlackKing && 
6312              BlackPawn <= toP && toP <= BlackKing &&
6313              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6314              !(fromP == BlackKing && toP == BlackRook && frc))) {
6315             /* Clicked again on same color piece -- changed his mind */
6316             second = (x == fromX && y == fromY);
6317            if(!second || !OnlyMove(&x, &y, TRUE)) {
6318             if (appData.highlightDragging) {
6319                 SetHighlights(x, y, -1, -1);
6320             } else {
6321                 ClearHighlights();
6322             }
6323             if (OKToStartUserMove(x, y)) {
6324                 fromX = x;
6325                 fromY = y;
6326                 MarkTargetSquares(0);
6327                 DragPieceBegin(xPix, yPix);
6328             }
6329             return;
6330            }
6331         }
6332         // ignore clicks on holdings
6333         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6334     }
6335
6336     if (clickType == Release && x == fromX && y == fromY) {
6337         DragPieceEnd(xPix, yPix);
6338         if (appData.animateDragging) {
6339             /* Undo animation damage if any */
6340             DrawPosition(FALSE, NULL);
6341         }
6342         if (second) {
6343             /* Second up/down in same square; just abort move */
6344             second = 0;
6345             fromX = fromY = -1;
6346             ClearHighlights();
6347             gotPremove = 0;
6348             ClearPremoveHighlights();
6349         } else {
6350             /* First upclick in same square; start click-click mode */
6351             SetHighlights(x, y, -1, -1);
6352         }
6353         return;
6354     }
6355
6356     /* we now have a different from- and (possibly off-board) to-square */
6357     /* Completed move */
6358     toX = x;
6359     toY = y;
6360     saveAnimate = appData.animate;
6361     if (clickType == Press) {
6362         /* Finish clickclick move */
6363         if (appData.animate || appData.highlightLastMove) {
6364             SetHighlights(fromX, fromY, toX, toY);
6365         } else {
6366             ClearHighlights();
6367         }
6368     } else {
6369         /* Finish drag move */
6370         if (appData.highlightLastMove) {
6371             SetHighlights(fromX, fromY, toX, toY);
6372         } else {
6373             ClearHighlights();
6374         }
6375         DragPieceEnd(xPix, yPix);
6376         /* Don't animate move and drag both */
6377         appData.animate = FALSE;
6378     }
6379
6380     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6381     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6382         ChessSquare piece = boards[currentMove][fromY][fromX];
6383         if(gameMode == EditPosition && piece != EmptySquare &&
6384            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6385             int n;
6386              
6387             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6388                 n = PieceToNumber(piece - (int)BlackPawn);
6389                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6390                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6391                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6392             } else
6393             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6394                 n = PieceToNumber(piece);
6395                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6396                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6397                 boards[currentMove][n][BOARD_WIDTH-2]++;
6398             }
6399             boards[currentMove][fromY][fromX] = EmptySquare;
6400         }
6401         ClearHighlights();
6402         fromX = fromY = -1;
6403         DrawPosition(TRUE, boards[currentMove]);
6404         return;
6405     }
6406
6407     // off-board moves should not be highlighted
6408     if(x < 0 || x < 0) ClearHighlights();
6409
6410     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6411         SetHighlights(fromX, fromY, toX, toY);
6412         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6413             // [HGM] super: promotion to captured piece selected from holdings
6414             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6415             promotionChoice = TRUE;
6416             // kludge follows to temporarily execute move on display, without promoting yet
6417             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6418             boards[currentMove][toY][toX] = p;
6419             DrawPosition(FALSE, boards[currentMove]);
6420             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6421             boards[currentMove][toY][toX] = q;
6422             DisplayMessage("Click in holdings to choose piece", "");
6423             return;
6424         }
6425         PromotionPopUp();
6426     } else {
6427         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6428         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6429         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6430         fromX = fromY = -1;
6431     }
6432     appData.animate = saveAnimate;
6433     if (appData.animate || appData.animateDragging) {
6434         /* Undo animation damage if needed */
6435         DrawPosition(FALSE, NULL);
6436     }
6437 }
6438
6439 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6440 {   // front-end-free part taken out of PieceMenuPopup
6441     int whichMenu; int xSqr, ySqr;
6442
6443     if(seekGraphUp) { // [HGM] seekgraph
6444         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6445         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6446         return -2;
6447     }
6448
6449     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6450          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6451         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6452         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6453         return -2;
6454     }
6455
6456     xSqr = EventToSquare(x, BOARD_WIDTH);
6457     ySqr = EventToSquare(y, BOARD_HEIGHT);
6458     if (action == Release) UnLoadPV(); // [HGM] pv
6459     if (action != Press) return -2; // return code to be ignored
6460     switch (gameMode) {
6461       case IcsExamining:
6462         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6463       case EditPosition:
6464         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6465         if (xSqr < 0 || ySqr < 0) return -1;\r
6466         whichMenu = 0; // edit-position menu
6467         break;
6468       case IcsObserving:
6469         if(!appData.icsEngineAnalyze) return -1;
6470       case IcsPlayingWhite:
6471       case IcsPlayingBlack:
6472         if(!appData.zippyPlay) goto noZip;
6473       case AnalyzeMode:
6474       case AnalyzeFile:
6475       case MachinePlaysWhite:
6476       case MachinePlaysBlack:
6477       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6478         if (!appData.dropMenu) {
6479           LoadPV(x, y);
6480           return 2; // flag front-end to grab mouse events
6481         }
6482         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6483            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6484       case EditGame:
6485       noZip:
6486         if (xSqr < 0 || ySqr < 0) return -1;
6487         if (!appData.dropMenu || appData.testLegality &&
6488             gameInfo.variant != VariantBughouse &&
6489             gameInfo.variant != VariantCrazyhouse) return -1;
6490         whichMenu = 1; // drop menu
6491         break;
6492       default:
6493         return -1;
6494     }
6495
6496     if (((*fromX = xSqr) < 0) ||
6497         ((*fromY = ySqr) < 0)) {
6498         *fromX = *fromY = -1;
6499         return -1;
6500     }
6501     if (flipView)
6502       *fromX = BOARD_WIDTH - 1 - *fromX;
6503     else
6504       *fromY = BOARD_HEIGHT - 1 - *fromY;
6505
6506     return whichMenu;
6507 }
6508
6509 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6510 {
6511 //    char * hint = lastHint;
6512     FrontEndProgramStats stats;
6513
6514     stats.which = cps == &first ? 0 : 1;
6515     stats.depth = cpstats->depth;
6516     stats.nodes = cpstats->nodes;
6517     stats.score = cpstats->score;
6518     stats.time = cpstats->time;
6519     stats.pv = cpstats->movelist;
6520     stats.hint = lastHint;
6521     stats.an_move_index = 0;
6522     stats.an_move_count = 0;
6523
6524     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6525         stats.hint = cpstats->move_name;
6526         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6527         stats.an_move_count = cpstats->nr_moves;
6528     }
6529
6530     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6531
6532     SetProgramStats( &stats );
6533 }
6534
6535 int
6536 Adjudicate(ChessProgramState *cps)
6537 {       // [HGM] some adjudications useful with buggy engines
6538         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6539         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6540         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6541         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6542         int k, count = 0; static int bare = 1;
6543         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6544         Boolean canAdjudicate = !appData.icsActive;
6545
6546         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6547         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6548             if( appData.testLegality )
6549             {   /* [HGM] Some more adjudications for obstinate engines */
6550                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6551                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6552                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6553                 static int moveCount = 6;
6554                 ChessMove result;
6555                 char *reason = NULL;
6556
6557                 /* Count what is on board. */
6558                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6559                 {   ChessSquare p = boards[forwardMostMove][i][j];
6560                     int m=i;
6561
6562                     switch((int) p)
6563                     {   /* count B,N,R and other of each side */
6564                         case WhiteKing:
6565                         case BlackKing:
6566                              NrK++; break; // [HGM] atomic: count Kings
6567                         case WhiteKnight:
6568                              NrWN++; break;
6569                         case WhiteBishop:
6570                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6571                              bishopsColor |= 1 << ((i^j)&1);
6572                              NrWB++; break;
6573                         case BlackKnight:
6574                              NrBN++; break;
6575                         case BlackBishop:
6576                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6577                              bishopsColor |= 1 << ((i^j)&1);
6578                              NrBB++; break;
6579                         case WhiteRook:
6580                              NrWR++; break;
6581                         case BlackRook:
6582                              NrBR++; break;
6583                         case WhiteQueen:
6584                              NrWQ++; break;
6585                         case BlackQueen:
6586                              NrBQ++; break;
6587                         case EmptySquare: 
6588                              break;
6589                         case BlackPawn:
6590                              m = 7-i;
6591                         case WhitePawn:
6592                              PawnAdvance += m; NrPawns++;
6593                     }
6594                     NrPieces += (p != EmptySquare);
6595                     NrW += ((int)p < (int)BlackPawn);
6596                     if(gameInfo.variant == VariantXiangqi && 
6597                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6598                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6599                         NrW -= ((int)p < (int)BlackPawn);
6600                     }
6601                 }
6602
6603                 /* Some material-based adjudications that have to be made before stalemate test */
6604                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6605                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6606                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6607                      if(canAdjudicate && appData.checkMates) {
6608                          if(engineOpponent)
6609                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6610                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6611                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6612                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6613                          return 1;
6614                      }
6615                 }
6616
6617                 /* Bare King in Shatranj (loses) or Losers (wins) */
6618                 if( NrW == 1 || NrPieces - NrW == 1) {
6619                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6620                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6621                      if(canAdjudicate && appData.checkMates) {
6622                          if(engineOpponent)
6623                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6624                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6625                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6626                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6627                          return 1;
6628                      }
6629                   } else
6630                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6631                   {    /* bare King */
6632                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6633                         if(canAdjudicate && appData.checkMates) {
6634                             /* but only adjudicate if adjudication enabled */
6635                             if(engineOpponent)
6636                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6637                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6638                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6639                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6640                             return 1;
6641                         }
6642                   }
6643                 } else bare = 1;
6644
6645
6646             // don't wait for engine to announce game end if we can judge ourselves
6647             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6648               case MT_CHECK:
6649                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6650                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6651                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6652                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6653                             checkCnt++;
6654                         if(checkCnt >= 2) {
6655                             reason = "Xboard adjudication: 3rd check";
6656                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6657                             break;
6658                         }
6659                     }
6660                 }
6661               case MT_NONE:
6662               default:
6663                 break;
6664               case MT_STALEMATE:
6665               case MT_STAINMATE:
6666                 reason = "Xboard adjudication: Stalemate";
6667                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6668                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6669                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6670                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6671                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6672                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6673                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6674                                                                         EP_CHECKMATE : EP_WINS);
6675                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6676                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6677                 }
6678                 break;
6679               case MT_CHECKMATE:
6680                 reason = "Xboard adjudication: Checkmate";
6681                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6682                 break;
6683             }
6684
6685                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6686                     case EP_STALEMATE:
6687                         result = GameIsDrawn; break;
6688                     case EP_CHECKMATE:
6689                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6690                     case EP_WINS:
6691                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6692                     default:
6693                         result = (ChessMove) 0;
6694                 }
6695                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6696                     if(engineOpponent)
6697                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6698                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6699                     GameEnds( result, reason, GE_XBOARD );
6700                     return 1;
6701                 }
6702
6703                 /* Next absolutely insufficient mating material. */
6704                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6705                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6706                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6707                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6708                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6709
6710                      /* always flag draws, for judging claims */
6711                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6712
6713                      if(canAdjudicate && appData.materialDraws) {
6714                          /* but only adjudicate them if adjudication enabled */
6715                          if(engineOpponent) {
6716                            SendToProgram("force\n", engineOpponent); // suppress reply
6717                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6718                          }
6719                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6720                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6721                          return 1;
6722                      }
6723                 }
6724
6725                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6726                 if(NrPieces == 4 && 
6727                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6728                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6729                    || NrWN==2 || NrBN==2     /* KNNK */
6730                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6731                   ) ) {
6732                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6733                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6734                           if(engineOpponent) {
6735                             SendToProgram("force\n", engineOpponent); // suppress reply
6736                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6737                           }
6738                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6739                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6740                           return 1;
6741                      }
6742                 } else moveCount = 6;
6743             }
6744         }
6745           
6746         if (appData.debugMode) { int i;
6747             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6748                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6749                     appData.drawRepeats);
6750             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6751               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6752             
6753         }
6754
6755         // Repetition draws and 50-move rule can be applied independently of legality testing
6756
6757                 /* Check for rep-draws */
6758                 count = 0;
6759                 for(k = forwardMostMove-2;
6760                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6761                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6762                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6763                     k-=2)
6764                 {   int rights=0;
6765                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6766                         /* compare castling rights */
6767                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6768                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6769                                 rights++; /* King lost rights, while rook still had them */
6770                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6771                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6772                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6773                                    rights++; /* but at least one rook lost them */
6774                         }
6775                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6776                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6777                                 rights++; 
6778                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6779                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6780                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6781                                    rights++;
6782                         }
6783                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6784                             && appData.drawRepeats > 1) {
6785                              /* adjudicate after user-specified nr of repeats */
6786                              if(engineOpponent) {
6787                                SendToProgram("force\n", engineOpponent); // suppress reply
6788                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6789                              }
6790                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6791                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6792                                 // [HGM] xiangqi: check for forbidden perpetuals
6793                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6794                                 for(m=forwardMostMove; m>k; m-=2) {
6795                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6796                                         ourPerpetual = 0; // the current mover did not always check
6797                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6798                                         hisPerpetual = 0; // the opponent did not always check
6799                                 }
6800                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6801                                                                         ourPerpetual, hisPerpetual);
6802                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6803                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6804                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6805                                     return 1;
6806                                 }
6807                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6808                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6809                                 // Now check for perpetual chases
6810                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6811                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6812                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6813                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6814                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6815                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6816                                         return 1;
6817                                     }
6818                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6819                                         break; // Abort repetition-checking loop.
6820                                 }
6821                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6822                              }
6823                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6824                              return 1;
6825                         }
6826                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6827                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6828                     }
6829                 }
6830
6831                 /* Now we test for 50-move draws. Determine ply count */
6832                 count = forwardMostMove;
6833                 /* look for last irreversble move */
6834                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6835                     count--;
6836                 /* if we hit starting position, add initial plies */
6837                 if( count == backwardMostMove )
6838                     count -= initialRulePlies;
6839                 count = forwardMostMove - count; 
6840                 if( count >= 100)
6841                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6842                          /* this is used to judge if draw claims are legal */
6843                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6844                          if(engineOpponent) {
6845                            SendToProgram("force\n", engineOpponent); // suppress reply
6846                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6847                          }
6848                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6849                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6850                          return 1;
6851                 }
6852
6853                 /* if draw offer is pending, treat it as a draw claim
6854                  * when draw condition present, to allow engines a way to
6855                  * claim draws before making their move to avoid a race
6856                  * condition occurring after their move
6857                  */
6858                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6859                          char *p = NULL;
6860                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6861                              p = "Draw claim: 50-move rule";
6862                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6863                              p = "Draw claim: 3-fold repetition";
6864                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6865                              p = "Draw claim: insufficient mating material";
6866                          if( p != NULL && canAdjudicate) {
6867                              if(engineOpponent) {
6868                                SendToProgram("force\n", engineOpponent); // suppress reply
6869                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6870                              }
6871                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6872                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6873                              return 1;
6874                          }
6875                 }
6876
6877                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6878                     if(engineOpponent) {
6879                       SendToProgram("force\n", engineOpponent); // suppress reply
6880                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6881                     }
6882                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6883                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6884                     return 1;
6885                 }
6886         return 0;
6887 }
6888
6889 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6890 {   // [HGM] book: this routine intercepts moves to simulate book replies
6891     char *bookHit = NULL;
6892
6893     //first determine if the incoming move brings opponent into his book
6894     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6895         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6896     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6897     if(bookHit != NULL && !cps->bookSuspend) {
6898         // make sure opponent is not going to reply after receiving move to book position
6899         SendToProgram("force\n", cps);
6900         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6901     }
6902     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6903     // now arrange restart after book miss
6904     if(bookHit) {
6905         // after a book hit we never send 'go', and the code after the call to this routine
6906         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6907         char buf[MSG_SIZ];
6908         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6909         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6910         SendToProgram(buf, cps);
6911         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6912     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6913         SendToProgram("go\n", cps);
6914         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6915     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6916         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6917             SendToProgram("go\n", cps); 
6918         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6919     }
6920     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6921 }
6922
6923 char *savedMessage;
6924 ChessProgramState *savedState;
6925 void DeferredBookMove(void)
6926 {
6927         if(savedState->lastPing != savedState->lastPong)
6928                     ScheduleDelayedEvent(DeferredBookMove, 10);
6929         else
6930         HandleMachineMove(savedMessage, savedState);
6931 }
6932
6933 void
6934 HandleMachineMove(message, cps)
6935      char *message;
6936      ChessProgramState *cps;
6937 {
6938     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6939     char realname[MSG_SIZ];
6940     int fromX, fromY, toX, toY;
6941     ChessMove moveType;
6942     char promoChar;
6943     char *p;
6944     int machineWhite;
6945     char *bookHit;
6946
6947     cps->userError = 0;
6948
6949 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6950     /*
6951      * Kludge to ignore BEL characters
6952      */
6953     while (*message == '\007') message++;
6954
6955     /*
6956      * [HGM] engine debug message: ignore lines starting with '#' character
6957      */
6958     if(cps->debug && *message == '#') return;
6959
6960     /*
6961      * Look for book output
6962      */
6963     if (cps == &first && bookRequested) {
6964         if (message[0] == '\t' || message[0] == ' ') {
6965             /* Part of the book output is here; append it */
6966             strcat(bookOutput, message);
6967             strcat(bookOutput, "  \n");
6968             return;
6969         } else if (bookOutput[0] != NULLCHAR) {
6970             /* All of book output has arrived; display it */
6971             char *p = bookOutput;
6972             while (*p != NULLCHAR) {
6973                 if (*p == '\t') *p = ' ';
6974                 p++;
6975             }
6976             DisplayInformation(bookOutput);
6977             bookRequested = FALSE;
6978             /* Fall through to parse the current output */
6979         }
6980     }
6981
6982     /*
6983      * Look for machine move.
6984      */
6985     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6986         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6987     {
6988         /* This method is only useful on engines that support ping */
6989         if (cps->lastPing != cps->lastPong) {
6990           if (gameMode == BeginningOfGame) {
6991             /* Extra move from before last new; ignore */
6992             if (appData.debugMode) {
6993                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6994             }
6995           } else {
6996             if (appData.debugMode) {
6997                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6998                         cps->which, gameMode);
6999             }
7000
7001             SendToProgram("undo\n", cps);
7002           }
7003           return;
7004         }
7005
7006         switch (gameMode) {
7007           case BeginningOfGame:
7008             /* Extra move from before last reset; ignore */
7009             if (appData.debugMode) {
7010                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7011             }
7012             return;
7013
7014           case EndOfGame:
7015           case IcsIdle:
7016           default:
7017             /* Extra move after we tried to stop.  The mode test is
7018                not a reliable way of detecting this problem, but it's
7019                the best we can do on engines that don't support ping.
7020             */
7021             if (appData.debugMode) {
7022                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7023                         cps->which, gameMode);
7024             }
7025             SendToProgram("undo\n", cps);
7026             return;
7027
7028           case MachinePlaysWhite:
7029           case IcsPlayingWhite:
7030             machineWhite = TRUE;
7031             break;
7032
7033           case MachinePlaysBlack:
7034           case IcsPlayingBlack:
7035             machineWhite = FALSE;
7036             break;
7037
7038           case TwoMachinesPlay:
7039             machineWhite = (cps->twoMachinesColor[0] == 'w');
7040             break;
7041         }
7042         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7043             if (appData.debugMode) {
7044                 fprintf(debugFP,
7045                         "Ignoring move out of turn by %s, gameMode %d"
7046                         ", forwardMost %d\n",
7047                         cps->which, gameMode, forwardMostMove);
7048             }
7049             return;
7050         }
7051
7052     if (appData.debugMode) { int f = forwardMostMove;
7053         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7054                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7055                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7056     }
7057         if(cps->alphaRank) AlphaRank(machineMove, 4);
7058         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7059                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7060             /* Machine move could not be parsed; ignore it. */
7061             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7062                     machineMove, cps->which);
7063             DisplayError(buf1, 0);
7064             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7065                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7066             if (gameMode == TwoMachinesPlay) {
7067               GameEnds(machineWhite ? BlackWins : WhiteWins,
7068                        buf1, GE_XBOARD);
7069             }
7070             return;
7071         }
7072
7073         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7074         /* So we have to redo legality test with true e.p. status here,  */
7075         /* to make sure an illegal e.p. capture does not slip through,   */
7076         /* to cause a forfeit on a justified illegal-move complaint      */
7077         /* of the opponent.                                              */
7078         if( gameMode==TwoMachinesPlay && appData.testLegality
7079             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7080                                                               ) {
7081            ChessMove moveType;
7082            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7083                              fromY, fromX, toY, toX, promoChar);
7084             if (appData.debugMode) {
7085                 int i;
7086                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7087                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7088                 fprintf(debugFP, "castling rights\n");
7089             }
7090             if(moveType == IllegalMove) {
7091                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7092                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7093                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7094                            buf1, GE_XBOARD);
7095                 return;
7096            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7097            /* [HGM] Kludge to handle engines that send FRC-style castling
7098               when they shouldn't (like TSCP-Gothic) */
7099            switch(moveType) {
7100              case WhiteASideCastleFR:
7101              case BlackASideCastleFR:
7102                toX+=2;
7103                currentMoveString[2]++;
7104                break;
7105              case WhiteHSideCastleFR:
7106              case BlackHSideCastleFR:
7107                toX--;
7108                currentMoveString[2]--;
7109                break;
7110              default: ; // nothing to do, but suppresses warning of pedantic compilers
7111            }
7112         }
7113         hintRequested = FALSE;
7114         lastHint[0] = NULLCHAR;
7115         bookRequested = FALSE;
7116         /* Program may be pondering now */
7117         cps->maybeThinking = TRUE;
7118         if (cps->sendTime == 2) cps->sendTime = 1;
7119         if (cps->offeredDraw) cps->offeredDraw--;
7120
7121         /* currentMoveString is set as a side-effect of ParseOneMove */
7122         strcpy(machineMove, currentMoveString);
7123         strcat(machineMove, "\n");
7124         strcpy(moveList[forwardMostMove], machineMove);
7125
7126         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7127
7128         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7129         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7130             int count = 0;
7131
7132             while( count < adjudicateLossPlies ) {
7133                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7134
7135                 if( count & 1 ) {
7136                     score = -score; /* Flip score for winning side */
7137                 }
7138
7139                 if( score > adjudicateLossThreshold ) {
7140                     break;
7141                 }
7142
7143                 count++;
7144             }
7145
7146             if( count >= adjudicateLossPlies ) {
7147                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7148
7149                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7150                     "Xboard adjudication", 
7151                     GE_XBOARD );
7152
7153                 return;
7154             }
7155         }
7156
7157         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7158
7159 #if ZIPPY
7160         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7161             first.initDone) {
7162           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7163                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7164                 SendToICS("draw ");
7165                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7166           }
7167           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7168           ics_user_moved = 1;
7169           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7170                 char buf[3*MSG_SIZ];
7171
7172                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7173                         programStats.score / 100.,
7174                         programStats.depth,
7175                         programStats.time / 100.,
7176                         (unsigned int)programStats.nodes,
7177                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7178                         programStats.movelist);
7179                 SendToICS(buf);
7180 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7181           }
7182         }
7183 #endif
7184
7185         /* [AS] Save move info and clear stats for next move */
7186         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7187         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7188         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7189         ClearProgramStats();
7190         thinkOutput[0] = NULLCHAR;
7191         hiddenThinkOutputState = 0;
7192
7193         bookHit = NULL;
7194         if (gameMode == TwoMachinesPlay) {
7195             /* [HGM] relaying draw offers moved to after reception of move */
7196             /* and interpreting offer as claim if it brings draw condition */
7197             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7198                 SendToProgram("draw\n", cps->other);
7199             }
7200             if (cps->other->sendTime) {
7201                 SendTimeRemaining(cps->other,
7202                                   cps->other->twoMachinesColor[0] == 'w');
7203             }
7204             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7205             if (firstMove && !bookHit) {
7206                 firstMove = FALSE;
7207                 if (cps->other->useColors) {
7208                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7209                 }
7210                 SendToProgram("go\n", cps->other);
7211             }
7212             cps->other->maybeThinking = TRUE;
7213         }
7214
7215         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7216         
7217         if (!pausing && appData.ringBellAfterMoves) {
7218             RingBell();
7219         }
7220
7221         /* 
7222          * Reenable menu items that were disabled while
7223          * machine was thinking
7224          */
7225         if (gameMode != TwoMachinesPlay)
7226             SetUserThinkingEnables();
7227
7228         // [HGM] book: after book hit opponent has received move and is now in force mode
7229         // force the book reply into it, and then fake that it outputted this move by jumping
7230         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7231         if(bookHit) {
7232                 static char bookMove[MSG_SIZ]; // a bit generous?
7233
7234                 strcpy(bookMove, "move ");
7235                 strcat(bookMove, bookHit);
7236                 message = bookMove;
7237                 cps = cps->other;
7238                 programStats.nodes = programStats.depth = programStats.time = 
7239                 programStats.score = programStats.got_only_move = 0;
7240                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7241
7242                 if(cps->lastPing != cps->lastPong) {
7243                     savedMessage = message; // args for deferred call
7244                     savedState = cps;
7245                     ScheduleDelayedEvent(DeferredBookMove, 10);
7246                     return;
7247                 }
7248                 goto FakeBookMove;
7249         }
7250
7251         return;
7252     }
7253
7254     /* Set special modes for chess engines.  Later something general
7255      *  could be added here; for now there is just one kludge feature,
7256      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7257      *  when "xboard" is given as an interactive command.
7258      */
7259     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7260         cps->useSigint = FALSE;
7261         cps->useSigterm = FALSE;
7262     }
7263     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7264       ParseFeatures(message+8, cps);
7265       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7266     }
7267
7268     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7269      * want this, I was asked to put it in, and obliged.
7270      */
7271     if (!strncmp(message, "setboard ", 9)) {
7272         Board initial_position;
7273
7274         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7275
7276         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7277             DisplayError(_("Bad FEN received from engine"), 0);
7278             return ;
7279         } else {
7280            Reset(TRUE, FALSE);
7281            CopyBoard(boards[0], initial_position);
7282            initialRulePlies = FENrulePlies;
7283            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7284            else gameMode = MachinePlaysBlack;                 
7285            DrawPosition(FALSE, boards[currentMove]);
7286         }
7287         return;
7288     }
7289
7290     /*
7291      * Look for communication commands
7292      */
7293     if (!strncmp(message, "telluser ", 9)) {
7294         DisplayNote(message + 9);
7295         return;
7296     }
7297     if (!strncmp(message, "tellusererror ", 14)) {
7298         cps->userError = 1;
7299         DisplayError(message + 14, 0);
7300         return;
7301     }
7302     if (!strncmp(message, "tellopponent ", 13)) {
7303       if (appData.icsActive) {
7304         if (loggedOn) {
7305           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7306           SendToICS(buf1);
7307         }
7308       } else {
7309         DisplayNote(message + 13);
7310       }
7311       return;
7312     }
7313     if (!strncmp(message, "tellothers ", 11)) {
7314       if (appData.icsActive) {
7315         if (loggedOn) {
7316           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7317           SendToICS(buf1);
7318         }
7319       }
7320       return;
7321     }
7322     if (!strncmp(message, "tellall ", 8)) {
7323       if (appData.icsActive) {
7324         if (loggedOn) {
7325           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7326           SendToICS(buf1);
7327         }
7328       } else {
7329         DisplayNote(message + 8);
7330       }
7331       return;
7332     }
7333     if (strncmp(message, "warning", 7) == 0) {
7334         /* Undocumented feature, use tellusererror in new code */
7335         DisplayError(message, 0);
7336         return;
7337     }
7338     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7339         strcpy(realname, cps->tidy);
7340         strcat(realname, " query");
7341         AskQuestion(realname, buf2, buf1, cps->pr);
7342         return;
7343     }
7344     /* Commands from the engine directly to ICS.  We don't allow these to be 
7345      *  sent until we are logged on. Crafty kibitzes have been known to 
7346      *  interfere with the login process.
7347      */
7348     if (loggedOn) {
7349         if (!strncmp(message, "tellics ", 8)) {
7350             SendToICS(message + 8);
7351             SendToICS("\n");
7352             return;
7353         }
7354         if (!strncmp(message, "tellicsnoalias ", 15)) {
7355             SendToICS(ics_prefix);
7356             SendToICS(message + 15);
7357             SendToICS("\n");
7358             return;
7359         }
7360         /* The following are for backward compatibility only */
7361         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7362             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7363             SendToICS(ics_prefix);
7364             SendToICS(message);
7365             SendToICS("\n");
7366             return;
7367         }
7368     }
7369     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7370         return;
7371     }
7372     /*
7373      * If the move is illegal, cancel it and redraw the board.
7374      * Also deal with other error cases.  Matching is rather loose
7375      * here to accommodate engines written before the spec.
7376      */
7377     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7378         strncmp(message, "Error", 5) == 0) {
7379         if (StrStr(message, "name") || 
7380             StrStr(message, "rating") || StrStr(message, "?") ||
7381             StrStr(message, "result") || StrStr(message, "board") ||
7382             StrStr(message, "bk") || StrStr(message, "computer") ||
7383             StrStr(message, "variant") || StrStr(message, "hint") ||
7384             StrStr(message, "random") || StrStr(message, "depth") ||
7385             StrStr(message, "accepted")) {
7386             return;
7387         }
7388         if (StrStr(message, "protover")) {
7389           /* Program is responding to input, so it's apparently done
7390              initializing, and this error message indicates it is
7391              protocol version 1.  So we don't need to wait any longer
7392              for it to initialize and send feature commands. */
7393           FeatureDone(cps, 1);
7394           cps->protocolVersion = 1;
7395           return;
7396         }
7397         cps->maybeThinking = FALSE;
7398
7399         if (StrStr(message, "draw")) {
7400             /* Program doesn't have "draw" command */
7401             cps->sendDrawOffers = 0;
7402             return;
7403         }
7404         if (cps->sendTime != 1 &&
7405             (StrStr(message, "time") || StrStr(message, "otim"))) {
7406           /* Program apparently doesn't have "time" or "otim" command */
7407           cps->sendTime = 0;
7408           return;
7409         }
7410         if (StrStr(message, "analyze")) {
7411             cps->analysisSupport = FALSE;
7412             cps->analyzing = FALSE;
7413             Reset(FALSE, TRUE);
7414             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7415             DisplayError(buf2, 0);
7416             return;
7417         }
7418         if (StrStr(message, "(no matching move)st")) {
7419           /* Special kludge for GNU Chess 4 only */
7420           cps->stKludge = TRUE;
7421           SendTimeControl(cps, movesPerSession, timeControl,
7422                           timeIncrement, appData.searchDepth,
7423                           searchTime);
7424           return;
7425         }
7426         if (StrStr(message, "(no matching move)sd")) {
7427           /* Special kludge for GNU Chess 4 only */
7428           cps->sdKludge = TRUE;
7429           SendTimeControl(cps, movesPerSession, timeControl,
7430                           timeIncrement, appData.searchDepth,
7431                           searchTime);
7432           return;
7433         }
7434         if (!StrStr(message, "llegal")) {
7435             return;
7436         }
7437         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7438             gameMode == IcsIdle) return;
7439         if (forwardMostMove <= backwardMostMove) return;
7440         if (pausing) PauseEvent();
7441       if(appData.forceIllegal) {
7442             // [HGM] illegal: machine refused move; force position after move into it
7443           SendToProgram("force\n", cps);
7444           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7445                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7446                 // when black is to move, while there might be nothing on a2 or black
7447                 // might already have the move. So send the board as if white has the move.
7448                 // But first we must change the stm of the engine, as it refused the last move
7449                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7450                 if(WhiteOnMove(forwardMostMove)) {
7451                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7452                     SendBoard(cps, forwardMostMove); // kludgeless board
7453                 } else {
7454                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7455                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7456                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7457                 }
7458           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7459             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7460                  gameMode == TwoMachinesPlay)
7461               SendToProgram("go\n", cps);
7462             return;
7463       } else
7464         if (gameMode == PlayFromGameFile) {
7465             /* Stop reading this game file */
7466             gameMode = EditGame;
7467             ModeHighlight();
7468         }
7469         currentMove = forwardMostMove-1;
7470         DisplayMove(currentMove-1); /* before DisplayMoveError */
7471         SwitchClocks(forwardMostMove-1); // [HGM] race
7472         DisplayBothClocks();
7473         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7474                 parseList[currentMove], cps->which);
7475         DisplayMoveError(buf1);
7476         DrawPosition(FALSE, boards[currentMove]);
7477
7478         /* [HGM] illegal-move claim should forfeit game when Xboard */
7479         /* only passes fully legal moves                            */
7480         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7481             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7482                                 "False illegal-move claim", GE_XBOARD );
7483         }
7484         return;
7485     }
7486     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7487         /* Program has a broken "time" command that
7488            outputs a string not ending in newline.
7489            Don't use it. */
7490         cps->sendTime = 0;
7491     }
7492     
7493     /*
7494      * If chess program startup fails, exit with an error message.
7495      * Attempts to recover here are futile.
7496      */
7497     if ((StrStr(message, "unknown host") != NULL)
7498         || (StrStr(message, "No remote directory") != NULL)
7499         || (StrStr(message, "not found") != NULL)
7500         || (StrStr(message, "No such file") != NULL)
7501         || (StrStr(message, "can't alloc") != NULL)
7502         || (StrStr(message, "Permission denied") != NULL)) {
7503
7504         cps->maybeThinking = FALSE;
7505         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7506                 cps->which, cps->program, cps->host, message);
7507         RemoveInputSource(cps->isr);
7508         DisplayFatalError(buf1, 0, 1);
7509         return;
7510     }
7511     
7512     /* 
7513      * Look for hint output
7514      */
7515     if (sscanf(message, "Hint: %s", buf1) == 1) {
7516         if (cps == &first && hintRequested) {
7517             hintRequested = FALSE;
7518             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7519                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7520                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7521                                     PosFlags(forwardMostMove),
7522                                     fromY, fromX, toY, toX, promoChar, buf1);
7523                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7524                 DisplayInformation(buf2);
7525             } else {
7526                 /* Hint move could not be parsed!? */
7527               snprintf(buf2, sizeof(buf2),
7528                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7529                         buf1, cps->which);
7530                 DisplayError(buf2, 0);
7531             }
7532         } else {
7533             strcpy(lastHint, buf1);
7534         }
7535         return;
7536     }
7537
7538     /*
7539      * Ignore other messages if game is not in progress
7540      */
7541     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7542         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7543
7544     /*
7545      * look for win, lose, draw, or draw offer
7546      */
7547     if (strncmp(message, "1-0", 3) == 0) {
7548         char *p, *q, *r = "";
7549         p = strchr(message, '{');
7550         if (p) {
7551             q = strchr(p, '}');
7552             if (q) {
7553                 *q = NULLCHAR;
7554                 r = p + 1;
7555             }
7556         }
7557         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7558         return;
7559     } else if (strncmp(message, "0-1", 3) == 0) {
7560         char *p, *q, *r = "";
7561         p = strchr(message, '{');
7562         if (p) {
7563             q = strchr(p, '}');
7564             if (q) {
7565                 *q = NULLCHAR;
7566                 r = p + 1;
7567             }
7568         }
7569         /* Kludge for Arasan 4.1 bug */
7570         if (strcmp(r, "Black resigns") == 0) {
7571             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7572             return;
7573         }
7574         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7575         return;
7576     } else if (strncmp(message, "1/2", 3) == 0) {
7577         char *p, *q, *r = "";
7578         p = strchr(message, '{');
7579         if (p) {
7580             q = strchr(p, '}');
7581             if (q) {
7582                 *q = NULLCHAR;
7583                 r = p + 1;
7584             }
7585         }
7586             
7587         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7588         return;
7589
7590     } else if (strncmp(message, "White resign", 12) == 0) {
7591         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7592         return;
7593     } else if (strncmp(message, "Black resign", 12) == 0) {
7594         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7595         return;
7596     } else if (strncmp(message, "White matches", 13) == 0 ||
7597                strncmp(message, "Black matches", 13) == 0   ) {
7598         /* [HGM] ignore GNUShogi noises */
7599         return;
7600     } else if (strncmp(message, "White", 5) == 0 &&
7601                message[5] != '(' &&
7602                StrStr(message, "Black") == NULL) {
7603         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7604         return;
7605     } else if (strncmp(message, "Black", 5) == 0 &&
7606                message[5] != '(') {
7607         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7608         return;
7609     } else if (strcmp(message, "resign") == 0 ||
7610                strcmp(message, "computer resigns") == 0) {
7611         switch (gameMode) {
7612           case MachinePlaysBlack:
7613           case IcsPlayingBlack:
7614             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7615             break;
7616           case MachinePlaysWhite:
7617           case IcsPlayingWhite:
7618             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7619             break;
7620           case TwoMachinesPlay:
7621             if (cps->twoMachinesColor[0] == 'w')
7622               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7623             else
7624               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7625             break;
7626           default:
7627             /* can't happen */
7628             break;
7629         }
7630         return;
7631     } else if (strncmp(message, "opponent mates", 14) == 0) {
7632         switch (gameMode) {
7633           case MachinePlaysBlack:
7634           case IcsPlayingBlack:
7635             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7636             break;
7637           case MachinePlaysWhite:
7638           case IcsPlayingWhite:
7639             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7640             break;
7641           case TwoMachinesPlay:
7642             if (cps->twoMachinesColor[0] == 'w')
7643               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7644             else
7645               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7646             break;
7647           default:
7648             /* can't happen */
7649             break;
7650         }
7651         return;
7652     } else if (strncmp(message, "computer mates", 14) == 0) {
7653         switch (gameMode) {
7654           case MachinePlaysBlack:
7655           case IcsPlayingBlack:
7656             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7657             break;
7658           case MachinePlaysWhite:
7659           case IcsPlayingWhite:
7660             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7661             break;
7662           case TwoMachinesPlay:
7663             if (cps->twoMachinesColor[0] == 'w')
7664               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7665             else
7666               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7667             break;
7668           default:
7669             /* can't happen */
7670             break;
7671         }
7672         return;
7673     } else if (strncmp(message, "checkmate", 9) == 0) {
7674         if (WhiteOnMove(forwardMostMove)) {
7675             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7676         } else {
7677             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7678         }
7679         return;
7680     } else if (strstr(message, "Draw") != NULL ||
7681                strstr(message, "game is a draw") != NULL) {
7682         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7683         return;
7684     } else if (strstr(message, "offer") != NULL &&
7685                strstr(message, "draw") != NULL) {
7686 #if ZIPPY
7687         if (appData.zippyPlay && first.initDone) {
7688             /* Relay offer to ICS */
7689             SendToICS(ics_prefix);
7690             SendToICS("draw\n");
7691         }
7692 #endif
7693         cps->offeredDraw = 2; /* valid until this engine moves twice */
7694         if (gameMode == TwoMachinesPlay) {
7695             if (cps->other->offeredDraw) {
7696                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7697             /* [HGM] in two-machine mode we delay relaying draw offer      */
7698             /* until after we also have move, to see if it is really claim */
7699             }
7700         } else if (gameMode == MachinePlaysWhite ||
7701                    gameMode == MachinePlaysBlack) {
7702           if (userOfferedDraw) {
7703             DisplayInformation(_("Machine accepts your draw offer"));
7704             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7705           } else {
7706             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7707           }
7708         }
7709     }
7710
7711     
7712     /*
7713      * Look for thinking output
7714      */
7715     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7716           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7717                                 ) {
7718         int plylev, mvleft, mvtot, curscore, time;
7719         char mvname[MOVE_LEN];
7720         u64 nodes; // [DM]
7721         char plyext;
7722         int ignore = FALSE;
7723         int prefixHint = FALSE;
7724         mvname[0] = NULLCHAR;
7725
7726         switch (gameMode) {
7727           case MachinePlaysBlack:
7728           case IcsPlayingBlack:
7729             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7730             break;
7731           case MachinePlaysWhite:
7732           case IcsPlayingWhite:
7733             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7734             break;
7735           case AnalyzeMode:
7736           case AnalyzeFile:
7737             break;
7738           case IcsObserving: /* [DM] icsEngineAnalyze */
7739             if (!appData.icsEngineAnalyze) ignore = TRUE;
7740             break;
7741           case TwoMachinesPlay:
7742             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7743                 ignore = TRUE;
7744             }
7745             break;
7746           default:
7747             ignore = TRUE;
7748             break;
7749         }
7750
7751         if (!ignore) {
7752             buf1[0] = NULLCHAR;
7753             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7754                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7755
7756                 if (plyext != ' ' && plyext != '\t') {
7757                     time *= 100;
7758                 }
7759
7760                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7761                 if( cps->scoreIsAbsolute && 
7762                     ( gameMode == MachinePlaysBlack ||
7763                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7764                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7765                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7766                      !WhiteOnMove(currentMove)
7767                     ) )
7768                 {
7769                     curscore = -curscore;
7770                 }
7771
7772
7773                 programStats.depth = plylev;
7774                 programStats.nodes = nodes;
7775                 programStats.time = time;
7776                 programStats.score = curscore;
7777                 programStats.got_only_move = 0;
7778
7779                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7780                         int ticklen;
7781
7782                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7783                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7784                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7785                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7786                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7787                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7788                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7789                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7790                 }
7791
7792                 /* Buffer overflow protection */
7793                 if (buf1[0] != NULLCHAR) {
7794                     if (strlen(buf1) >= sizeof(programStats.movelist)
7795                         && appData.debugMode) {
7796                         fprintf(debugFP,
7797                                 "PV is too long; using the first %u bytes.\n",
7798                                 (unsigned) sizeof(programStats.movelist) - 1);
7799                     }
7800
7801                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7802                 } else {
7803                     sprintf(programStats.movelist, " no PV\n");
7804                 }
7805
7806                 if (programStats.seen_stat) {
7807                     programStats.ok_to_send = 1;
7808                 }
7809
7810                 if (strchr(programStats.movelist, '(') != NULL) {
7811                     programStats.line_is_book = 1;
7812                     programStats.nr_moves = 0;
7813                     programStats.moves_left = 0;
7814                 } else {
7815                     programStats.line_is_book = 0;
7816                 }
7817
7818                 SendProgramStatsToFrontend( cps, &programStats );
7819
7820                 /* 
7821                     [AS] Protect the thinkOutput buffer from overflow... this
7822                     is only useful if buf1 hasn't overflowed first!
7823                 */
7824                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7825                         plylev, 
7826                         (gameMode == TwoMachinesPlay ?
7827                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7828                         ((double) curscore) / 100.0,
7829                         prefixHint ? lastHint : "",
7830                         prefixHint ? " " : "" );
7831
7832                 if( buf1[0] != NULLCHAR ) {
7833                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7834
7835                     if( strlen(buf1) > max_len ) {
7836                         if( appData.debugMode) {
7837                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7838                         }
7839                         buf1[max_len+1] = '\0';
7840                     }
7841
7842                     strcat( thinkOutput, buf1 );
7843                 }
7844
7845                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7846                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7847                     DisplayMove(currentMove - 1);
7848                 }
7849                 return;
7850
7851             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7852                 /* crafty (9.25+) says "(only move) <move>"
7853                  * if there is only 1 legal move
7854                  */
7855                 sscanf(p, "(only move) %s", buf1);
7856                 sprintf(thinkOutput, "%s (only move)", buf1);
7857                 sprintf(programStats.movelist, "%s (only move)", buf1);
7858                 programStats.depth = 1;
7859                 programStats.nr_moves = 1;
7860                 programStats.moves_left = 1;
7861                 programStats.nodes = 1;
7862                 programStats.time = 1;
7863                 programStats.got_only_move = 1;
7864
7865                 /* Not really, but we also use this member to
7866                    mean "line isn't going to change" (Crafty
7867                    isn't searching, so stats won't change) */
7868                 programStats.line_is_book = 1;
7869
7870                 SendProgramStatsToFrontend( cps, &programStats );
7871                 
7872                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7873                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7874                     DisplayMove(currentMove - 1);
7875                 }
7876                 return;
7877             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7878                               &time, &nodes, &plylev, &mvleft,
7879                               &mvtot, mvname) >= 5) {
7880                 /* The stat01: line is from Crafty (9.29+) in response
7881                    to the "." command */
7882                 programStats.seen_stat = 1;
7883                 cps->maybeThinking = TRUE;
7884
7885                 if (programStats.got_only_move || !appData.periodicUpdates)
7886                   return;
7887
7888                 programStats.depth = plylev;
7889                 programStats.time = time;
7890                 programStats.nodes = nodes;
7891                 programStats.moves_left = mvleft;
7892                 programStats.nr_moves = mvtot;
7893                 strcpy(programStats.move_name, mvname);
7894                 programStats.ok_to_send = 1;
7895                 programStats.movelist[0] = '\0';
7896
7897                 SendProgramStatsToFrontend( cps, &programStats );
7898
7899                 return;
7900
7901             } else if (strncmp(message,"++",2) == 0) {
7902                 /* Crafty 9.29+ outputs this */
7903                 programStats.got_fail = 2;
7904                 return;
7905
7906             } else if (strncmp(message,"--",2) == 0) {
7907                 /* Crafty 9.29+ outputs this */
7908                 programStats.got_fail = 1;
7909                 return;
7910
7911             } else if (thinkOutput[0] != NULLCHAR &&
7912                        strncmp(message, "    ", 4) == 0) {
7913                 unsigned message_len;
7914
7915                 p = message;
7916                 while (*p && *p == ' ') p++;
7917
7918                 message_len = strlen( p );
7919
7920                 /* [AS] Avoid buffer overflow */
7921                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7922                     strcat(thinkOutput, " ");
7923                     strcat(thinkOutput, p);
7924                 }
7925
7926                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7927                     strcat(programStats.movelist, " ");
7928                     strcat(programStats.movelist, p);
7929                 }
7930
7931                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7932                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7933                     DisplayMove(currentMove - 1);
7934                 }
7935                 return;
7936             }
7937         }
7938         else {
7939             buf1[0] = NULLCHAR;
7940
7941             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7942                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7943             {
7944                 ChessProgramStats cpstats;
7945
7946                 if (plyext != ' ' && plyext != '\t') {
7947                     time *= 100;
7948                 }
7949
7950                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7951                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7952                     curscore = -curscore;
7953                 }
7954
7955                 cpstats.depth = plylev;
7956                 cpstats.nodes = nodes;
7957                 cpstats.time = time;
7958                 cpstats.score = curscore;
7959                 cpstats.got_only_move = 0;
7960                 cpstats.movelist[0] = '\0';
7961
7962                 if (buf1[0] != NULLCHAR) {
7963                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7964                 }
7965
7966                 cpstats.ok_to_send = 0;
7967                 cpstats.line_is_book = 0;
7968                 cpstats.nr_moves = 0;
7969                 cpstats.moves_left = 0;
7970
7971                 SendProgramStatsToFrontend( cps, &cpstats );
7972             }
7973         }
7974     }
7975 }
7976
7977
7978 /* Parse a game score from the character string "game", and
7979    record it as the history of the current game.  The game
7980    score is NOT assumed to start from the standard position. 
7981    The display is not updated in any way.
7982    */
7983 void
7984 ParseGameHistory(game)
7985      char *game;
7986 {
7987     ChessMove moveType;
7988     int fromX, fromY, toX, toY, boardIndex;
7989     char promoChar;
7990     char *p, *q;
7991     char buf[MSG_SIZ];
7992
7993     if (appData.debugMode)
7994       fprintf(debugFP, "Parsing game history: %s\n", game);
7995
7996     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7997     gameInfo.site = StrSave(appData.icsHost);
7998     gameInfo.date = PGNDate();
7999     gameInfo.round = StrSave("-");
8000
8001     /* Parse out names of players */
8002     while (*game == ' ') game++;
8003     p = buf;
8004     while (*game != ' ') *p++ = *game++;
8005     *p = NULLCHAR;
8006     gameInfo.white = StrSave(buf);
8007     while (*game == ' ') game++;
8008     p = buf;
8009     while (*game != ' ' && *game != '\n') *p++ = *game++;
8010     *p = NULLCHAR;
8011     gameInfo.black = StrSave(buf);
8012
8013     /* Parse moves */
8014     boardIndex = blackPlaysFirst ? 1 : 0;
8015     yynewstr(game);
8016     for (;;) {
8017         yyboardindex = boardIndex;
8018         moveType = (ChessMove) yylex();
8019         switch (moveType) {
8020           case IllegalMove:             /* maybe suicide chess, etc. */
8021   if (appData.debugMode) {
8022     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8023     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8024     setbuf(debugFP, NULL);
8025   }
8026           case WhitePromotionChancellor:
8027           case BlackPromotionChancellor:
8028           case WhitePromotionArchbishop:
8029           case BlackPromotionArchbishop:
8030           case WhitePromotionQueen:
8031           case BlackPromotionQueen:
8032           case WhitePromotionRook:
8033           case BlackPromotionRook:
8034           case WhitePromotionBishop:
8035           case BlackPromotionBishop:
8036           case WhitePromotionKnight:
8037           case BlackPromotionKnight:
8038           case WhitePromotionKing:
8039           case BlackPromotionKing:
8040           case NormalMove:
8041           case WhiteCapturesEnPassant:
8042           case BlackCapturesEnPassant:
8043           case WhiteKingSideCastle:
8044           case WhiteQueenSideCastle:
8045           case BlackKingSideCastle:
8046           case BlackQueenSideCastle:
8047           case WhiteKingSideCastleWild:
8048           case WhiteQueenSideCastleWild:
8049           case BlackKingSideCastleWild:
8050           case BlackQueenSideCastleWild:
8051           /* PUSH Fabien */
8052           case WhiteHSideCastleFR:
8053           case WhiteASideCastleFR:
8054           case BlackHSideCastleFR:
8055           case BlackASideCastleFR:
8056           /* POP Fabien */
8057             fromX = currentMoveString[0] - AAA;
8058             fromY = currentMoveString[1] - ONE;
8059             toX = currentMoveString[2] - AAA;
8060             toY = currentMoveString[3] - ONE;
8061             promoChar = currentMoveString[4];
8062             break;
8063           case WhiteDrop:
8064           case BlackDrop:
8065             fromX = moveType == WhiteDrop ?
8066               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8067             (int) CharToPiece(ToLower(currentMoveString[0]));
8068             fromY = DROP_RANK;
8069             toX = currentMoveString[2] - AAA;
8070             toY = currentMoveString[3] - ONE;
8071             promoChar = NULLCHAR;
8072             break;
8073           case AmbiguousMove:
8074             /* bug? */
8075             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8076   if (appData.debugMode) {
8077     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8078     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8079     setbuf(debugFP, NULL);
8080   }
8081             DisplayError(buf, 0);
8082             return;
8083           case ImpossibleMove:
8084             /* bug? */
8085             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8086   if (appData.debugMode) {
8087     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8088     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8089     setbuf(debugFP, NULL);
8090   }
8091             DisplayError(buf, 0);
8092             return;
8093           case (ChessMove) 0:   /* end of file */
8094             if (boardIndex < backwardMostMove) {
8095                 /* Oops, gap.  How did that happen? */
8096                 DisplayError(_("Gap in move list"), 0);
8097                 return;
8098             }
8099             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8100             if (boardIndex > forwardMostMove) {
8101                 forwardMostMove = boardIndex;
8102             }
8103             return;
8104           case ElapsedTime:
8105             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8106                 strcat(parseList[boardIndex-1], " ");
8107                 strcat(parseList[boardIndex-1], yy_text);
8108             }
8109             continue;
8110           case Comment:
8111           case PGNTag:
8112           case NAG:
8113           default:
8114             /* ignore */
8115             continue;
8116           case WhiteWins:
8117           case BlackWins:
8118           case GameIsDrawn:
8119           case GameUnfinished:
8120             if (gameMode == IcsExamining) {
8121                 if (boardIndex < backwardMostMove) {
8122                     /* Oops, gap.  How did that happen? */
8123                     return;
8124                 }
8125                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8126                 return;
8127             }
8128             gameInfo.result = moveType;
8129             p = strchr(yy_text, '{');
8130             if (p == NULL) p = strchr(yy_text, '(');
8131             if (p == NULL) {
8132                 p = yy_text;
8133                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8134             } else {
8135                 q = strchr(p, *p == '{' ? '}' : ')');
8136                 if (q != NULL) *q = NULLCHAR;
8137                 p++;
8138             }
8139             gameInfo.resultDetails = StrSave(p);
8140             continue;
8141         }
8142         if (boardIndex >= forwardMostMove &&
8143             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8144             backwardMostMove = blackPlaysFirst ? 1 : 0;
8145             return;
8146         }
8147         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8148                                  fromY, fromX, toY, toX, promoChar,
8149                                  parseList[boardIndex]);
8150         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8151         /* currentMoveString is set as a side-effect of yylex */
8152         strcpy(moveList[boardIndex], currentMoveString);
8153         strcat(moveList[boardIndex], "\n");
8154         boardIndex++;
8155         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8156         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8157           case MT_NONE:
8158           case MT_STALEMATE:
8159           default:
8160             break;
8161           case MT_CHECK:
8162             if(gameInfo.variant != VariantShogi)
8163                 strcat(parseList[boardIndex - 1], "+");
8164             break;
8165           case MT_CHECKMATE:
8166           case MT_STAINMATE:
8167             strcat(parseList[boardIndex - 1], "#");
8168             break;
8169         }
8170     }
8171 }
8172
8173
8174 /* Apply a move to the given board  */
8175 void
8176 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8177      int fromX, fromY, toX, toY;
8178      int promoChar;
8179      Board board;
8180 {
8181   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8182   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8183
8184     /* [HGM] compute & store e.p. status and castling rights for new position */
8185     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8186     { int i;
8187
8188       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8189       oldEP = (signed char)board[EP_STATUS];
8190       board[EP_STATUS] = EP_NONE;
8191
8192       if( board[toY][toX] != EmptySquare ) 
8193            board[EP_STATUS] = EP_CAPTURE;  
8194
8195       if( board[fromY][fromX] == WhitePawn ) {
8196            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8197                board[EP_STATUS] = EP_PAWN_MOVE;
8198            if( toY-fromY==2) {
8199                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8200                         gameInfo.variant != VariantBerolina || toX < fromX)
8201                       board[EP_STATUS] = toX | berolina;
8202                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8203                         gameInfo.variant != VariantBerolina || toX > fromX) 
8204                       board[EP_STATUS] = toX;
8205            }
8206       } else 
8207       if( board[fromY][fromX] == BlackPawn ) {
8208            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8209                board[EP_STATUS] = EP_PAWN_MOVE; 
8210            if( toY-fromY== -2) {
8211                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8212                         gameInfo.variant != VariantBerolina || toX < fromX)
8213                       board[EP_STATUS] = toX | berolina;
8214                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8215                         gameInfo.variant != VariantBerolina || toX > fromX) 
8216                       board[EP_STATUS] = toX;
8217            }
8218        }
8219
8220        for(i=0; i<nrCastlingRights; i++) {
8221            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8222               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8223              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8224        }
8225
8226     }
8227
8228   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8229   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8230        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8231          
8232   if (fromX == toX && fromY == toY) return;
8233
8234   if (fromY == DROP_RANK) {
8235         /* must be first */
8236         piece = board[toY][toX] = (ChessSquare) fromX;
8237   } else {
8238      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8239      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8240      if(gameInfo.variant == VariantKnightmate)
8241          king += (int) WhiteUnicorn - (int) WhiteKing;
8242
8243     /* Code added by Tord: */
8244     /* FRC castling assumed when king captures friendly rook. */
8245     if (board[fromY][fromX] == WhiteKing &&
8246              board[toY][toX] == WhiteRook) {
8247       board[fromY][fromX] = EmptySquare;
8248       board[toY][toX] = EmptySquare;
8249       if(toX > fromX) {
8250         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8251       } else {
8252         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8253       }
8254     } else if (board[fromY][fromX] == BlackKing &&
8255                board[toY][toX] == BlackRook) {
8256       board[fromY][fromX] = EmptySquare;
8257       board[toY][toX] = EmptySquare;
8258       if(toX > fromX) {
8259         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8260       } else {
8261         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8262       }
8263     /* End of code added by Tord */
8264
8265     } else if (board[fromY][fromX] == king
8266         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8267         && toY == fromY && toX > fromX+1) {
8268         board[fromY][fromX] = EmptySquare;
8269         board[toY][toX] = king;
8270         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8271         board[fromY][BOARD_RGHT-1] = EmptySquare;
8272     } else if (board[fromY][fromX] == king
8273         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8274                && toY == fromY && toX < fromX-1) {
8275         board[fromY][fromX] = EmptySquare;
8276         board[toY][toX] = king;
8277         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8278         board[fromY][BOARD_LEFT] = EmptySquare;
8279     } else if (board[fromY][fromX] == WhitePawn
8280                && toY >= BOARD_HEIGHT-promoRank
8281                && gameInfo.variant != VariantXiangqi
8282                ) {
8283         /* white pawn promotion */
8284         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8285         if (board[toY][toX] == EmptySquare) {
8286             board[toY][toX] = WhiteQueen;
8287         }
8288         if(gameInfo.variant==VariantBughouse ||
8289            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8290             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8291         board[fromY][fromX] = EmptySquare;
8292     } else if ((fromY == BOARD_HEIGHT-4)
8293                && (toX != fromX)
8294                && gameInfo.variant != VariantXiangqi
8295                && gameInfo.variant != VariantBerolina
8296                && (board[fromY][fromX] == WhitePawn)
8297                && (board[toY][toX] == EmptySquare)) {
8298         board[fromY][fromX] = EmptySquare;
8299         board[toY][toX] = WhitePawn;
8300         captured = board[toY - 1][toX];
8301         board[toY - 1][toX] = EmptySquare;
8302     } else if ((fromY == BOARD_HEIGHT-4)
8303                && (toX == fromX)
8304                && gameInfo.variant == VariantBerolina
8305                && (board[fromY][fromX] == WhitePawn)
8306                && (board[toY][toX] == EmptySquare)) {
8307         board[fromY][fromX] = EmptySquare;
8308         board[toY][toX] = WhitePawn;
8309         if(oldEP & EP_BEROLIN_A) {
8310                 captured = board[fromY][fromX-1];
8311                 board[fromY][fromX-1] = EmptySquare;
8312         }else{  captured = board[fromY][fromX+1];
8313                 board[fromY][fromX+1] = EmptySquare;
8314         }
8315     } else if (board[fromY][fromX] == king
8316         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8317                && toY == fromY && toX > fromX+1) {
8318         board[fromY][fromX] = EmptySquare;
8319         board[toY][toX] = king;
8320         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8321         board[fromY][BOARD_RGHT-1] = EmptySquare;
8322     } else if (board[fromY][fromX] == king
8323         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8324                && toY == fromY && toX < fromX-1) {
8325         board[fromY][fromX] = EmptySquare;
8326         board[toY][toX] = king;
8327         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8328         board[fromY][BOARD_LEFT] = EmptySquare;
8329     } else if (fromY == 7 && fromX == 3
8330                && board[fromY][fromX] == BlackKing
8331                && toY == 7 && toX == 5) {
8332         board[fromY][fromX] = EmptySquare;
8333         board[toY][toX] = BlackKing;
8334         board[fromY][7] = EmptySquare;
8335         board[toY][4] = BlackRook;
8336     } else if (fromY == 7 && fromX == 3
8337                && board[fromY][fromX] == BlackKing
8338                && toY == 7 && toX == 1) {
8339         board[fromY][fromX] = EmptySquare;
8340         board[toY][toX] = BlackKing;
8341         board[fromY][0] = EmptySquare;
8342         board[toY][2] = BlackRook;
8343     } else if (board[fromY][fromX] == BlackPawn
8344                && toY < promoRank
8345                && gameInfo.variant != VariantXiangqi
8346                ) {
8347         /* black pawn promotion */
8348         board[toY][toX] = CharToPiece(ToLower(promoChar));
8349         if (board[toY][toX] == EmptySquare) {
8350             board[toY][toX] = BlackQueen;
8351         }
8352         if(gameInfo.variant==VariantBughouse ||
8353            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8354             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8355         board[fromY][fromX] = EmptySquare;
8356     } else if ((fromY == 3)
8357                && (toX != fromX)
8358                && gameInfo.variant != VariantXiangqi
8359                && gameInfo.variant != VariantBerolina
8360                && (board[fromY][fromX] == BlackPawn)
8361                && (board[toY][toX] == EmptySquare)) {
8362         board[fromY][fromX] = EmptySquare;
8363         board[toY][toX] = BlackPawn;
8364         captured = board[toY + 1][toX];
8365         board[toY + 1][toX] = EmptySquare;
8366     } else if ((fromY == 3)
8367                && (toX == fromX)
8368                && gameInfo.variant == VariantBerolina
8369                && (board[fromY][fromX] == BlackPawn)
8370                && (board[toY][toX] == EmptySquare)) {
8371         board[fromY][fromX] = EmptySquare;
8372         board[toY][toX] = BlackPawn;
8373         if(oldEP & EP_BEROLIN_A) {
8374                 captured = board[fromY][fromX-1];
8375                 board[fromY][fromX-1] = EmptySquare;
8376         }else{  captured = board[fromY][fromX+1];
8377                 board[fromY][fromX+1] = EmptySquare;
8378         }
8379     } else {
8380         board[toY][toX] = board[fromY][fromX];
8381         board[fromY][fromX] = EmptySquare;
8382     }
8383
8384     /* [HGM] now we promote for Shogi, if needed */
8385     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8386         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8387   }
8388
8389     if (gameInfo.holdingsWidth != 0) {
8390
8391       /* !!A lot more code needs to be written to support holdings  */
8392       /* [HGM] OK, so I have written it. Holdings are stored in the */
8393       /* penultimate board files, so they are automaticlly stored   */
8394       /* in the game history.                                       */
8395       if (fromY == DROP_RANK) {
8396         /* Delete from holdings, by decreasing count */
8397         /* and erasing image if necessary            */
8398         p = (int) fromX;
8399         if(p < (int) BlackPawn) { /* white drop */
8400              p -= (int)WhitePawn;
8401                  p = PieceToNumber((ChessSquare)p);
8402              if(p >= gameInfo.holdingsSize) p = 0;
8403              if(--board[p][BOARD_WIDTH-2] <= 0)
8404                   board[p][BOARD_WIDTH-1] = EmptySquare;
8405              if((int)board[p][BOARD_WIDTH-2] < 0)
8406                         board[p][BOARD_WIDTH-2] = 0;
8407         } else {                  /* black drop */
8408              p -= (int)BlackPawn;
8409                  p = PieceToNumber((ChessSquare)p);
8410              if(p >= gameInfo.holdingsSize) p = 0;
8411              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8412                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8413              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8414                         board[BOARD_HEIGHT-1-p][1] = 0;
8415         }
8416       }
8417       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8418           && gameInfo.variant != VariantBughouse        ) {
8419         /* [HGM] holdings: Add to holdings, if holdings exist */
8420         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8421                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8422                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8423         }
8424         p = (int) captured;
8425         if (p >= (int) BlackPawn) {
8426           p -= (int)BlackPawn;
8427           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8428                   /* in Shogi restore piece to its original  first */
8429                   captured = (ChessSquare) (DEMOTED captured);
8430                   p = DEMOTED p;
8431           }
8432           p = PieceToNumber((ChessSquare)p);
8433           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8434           board[p][BOARD_WIDTH-2]++;
8435           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8436         } else {
8437           p -= (int)WhitePawn;
8438           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8439                   captured = (ChessSquare) (DEMOTED captured);
8440                   p = DEMOTED p;
8441           }
8442           p = PieceToNumber((ChessSquare)p);
8443           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8444           board[BOARD_HEIGHT-1-p][1]++;
8445           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8446         }
8447       }
8448     } else if (gameInfo.variant == VariantAtomic) {
8449       if (captured != EmptySquare) {
8450         int y, x;
8451         for (y = toY-1; y <= toY+1; y++) {
8452           for (x = toX-1; x <= toX+1; x++) {
8453             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8454                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8455               board[y][x] = EmptySquare;
8456             }
8457           }
8458         }
8459         board[toY][toX] = EmptySquare;
8460       }
8461     }
8462     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8463         /* [HGM] Shogi promotions */
8464         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8465     }
8466
8467     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8468                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8469         // [HGM] superchess: take promotion piece out of holdings
8470         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8471         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8472             if(!--board[k][BOARD_WIDTH-2])
8473                 board[k][BOARD_WIDTH-1] = EmptySquare;
8474         } else {
8475             if(!--board[BOARD_HEIGHT-1-k][1])
8476                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8477         }
8478     }
8479
8480 }
8481
8482 /* Updates forwardMostMove */
8483 void
8484 MakeMove(fromX, fromY, toX, toY, promoChar)
8485      int fromX, fromY, toX, toY;
8486      int promoChar;
8487 {
8488 //    forwardMostMove++; // [HGM] bare: moved downstream
8489
8490     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8491         int timeLeft; static int lastLoadFlag=0; int king, piece;
8492         piece = boards[forwardMostMove][fromY][fromX];
8493         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8494         if(gameInfo.variant == VariantKnightmate)
8495             king += (int) WhiteUnicorn - (int) WhiteKing;
8496         if(forwardMostMove == 0) {
8497             if(blackPlaysFirst) 
8498                 fprintf(serverMoves, "%s;", second.tidy);
8499             fprintf(serverMoves, "%s;", first.tidy);
8500             if(!blackPlaysFirst) 
8501                 fprintf(serverMoves, "%s;", second.tidy);
8502         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8503         lastLoadFlag = loadFlag;
8504         // print base move
8505         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8506         // print castling suffix
8507         if( toY == fromY && piece == king ) {
8508             if(toX-fromX > 1)
8509                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8510             if(fromX-toX >1)
8511                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8512         }
8513         // e.p. suffix
8514         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8515              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8516              boards[forwardMostMove][toY][toX] == EmptySquare
8517              && fromX != toX )
8518                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8519         // promotion suffix
8520         if(promoChar != NULLCHAR)
8521                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8522         if(!loadFlag) {
8523             fprintf(serverMoves, "/%d/%d",
8524                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8525             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8526             else                      timeLeft = blackTimeRemaining/1000;
8527             fprintf(serverMoves, "/%d", timeLeft);
8528         }
8529         fflush(serverMoves);
8530     }
8531
8532     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8533       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8534                         0, 1);
8535       return;
8536     }
8537     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8538     if (commentList[forwardMostMove+1] != NULL) {
8539         free(commentList[forwardMostMove+1]);
8540         commentList[forwardMostMove+1] = NULL;
8541     }
8542     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8543     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8544     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8545     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8546     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8547     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8548     gameInfo.result = GameUnfinished;
8549     if (gameInfo.resultDetails != NULL) {
8550         free(gameInfo.resultDetails);
8551         gameInfo.resultDetails = NULL;
8552     }
8553     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8554                               moveList[forwardMostMove - 1]);
8555     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8556                              PosFlags(forwardMostMove - 1),
8557                              fromY, fromX, toY, toX, promoChar,
8558                              parseList[forwardMostMove - 1]);
8559     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8560       case MT_NONE:
8561       case MT_STALEMATE:
8562       default:
8563         break;
8564       case MT_CHECK:
8565         if(gameInfo.variant != VariantShogi)
8566             strcat(parseList[forwardMostMove - 1], "+");
8567         break;
8568       case MT_CHECKMATE:
8569       case MT_STAINMATE:
8570         strcat(parseList[forwardMostMove - 1], "#");
8571         break;
8572     }
8573     if (appData.debugMode) {
8574         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8575     }
8576
8577 }
8578
8579 /* Updates currentMove if not pausing */
8580 void
8581 ShowMove(fromX, fromY, toX, toY)
8582 {
8583     int instant = (gameMode == PlayFromGameFile) ?
8584         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8585     if(appData.noGUI) return;
8586     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8587         if (!instant) {
8588             if (forwardMostMove == currentMove + 1) {
8589                 AnimateMove(boards[forwardMostMove - 1],
8590                             fromX, fromY, toX, toY);
8591             }
8592             if (appData.highlightLastMove) {
8593                 SetHighlights(fromX, fromY, toX, toY);
8594             }
8595         }
8596         currentMove = forwardMostMove;
8597     }
8598
8599     if (instant) return;
8600
8601     DisplayMove(currentMove - 1);
8602     DrawPosition(FALSE, boards[currentMove]);
8603     DisplayBothClocks();
8604     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8605 }
8606
8607 void SendEgtPath(ChessProgramState *cps)
8608 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8609         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8610
8611         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8612
8613         while(*p) {
8614             char c, *q = name+1, *r, *s;
8615
8616             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8617             while(*p && *p != ',') *q++ = *p++;
8618             *q++ = ':'; *q = 0;
8619             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8620                 strcmp(name, ",nalimov:") == 0 ) {
8621                 // take nalimov path from the menu-changeable option first, if it is defined
8622                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8623                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8624             } else
8625             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8626                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8627                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8628                 s = r = StrStr(s, ":") + 1; // beginning of path info
8629                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8630                 c = *r; *r = 0;             // temporarily null-terminate path info
8631                     *--q = 0;               // strip of trailig ':' from name
8632                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8633                 *r = c;
8634                 SendToProgram(buf,cps);     // send egtbpath command for this format
8635             }
8636             if(*p == ',') p++; // read away comma to position for next format name
8637         }
8638 }
8639
8640 void
8641 InitChessProgram(cps, setup)
8642      ChessProgramState *cps;
8643      int setup; /* [HGM] needed to setup FRC opening position */
8644 {
8645     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8646     if (appData.noChessProgram) return;
8647     hintRequested = FALSE;
8648     bookRequested = FALSE;
8649
8650     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8651     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8652     if(cps->memSize) { /* [HGM] memory */
8653         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8654         SendToProgram(buf, cps);
8655     }
8656     SendEgtPath(cps); /* [HGM] EGT */
8657     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8658         sprintf(buf, "cores %d\n", appData.smpCores);
8659         SendToProgram(buf, cps);
8660     }
8661
8662     SendToProgram(cps->initString, cps);
8663     if (gameInfo.variant != VariantNormal &&
8664         gameInfo.variant != VariantLoadable
8665         /* [HGM] also send variant if board size non-standard */
8666         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8667                                             ) {
8668       char *v = VariantName(gameInfo.variant);
8669       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8670         /* [HGM] in protocol 1 we have to assume all variants valid */
8671         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8672         DisplayFatalError(buf, 0, 1);
8673         return;
8674       }
8675
8676       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8677       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8678       if( gameInfo.variant == VariantXiangqi )
8679            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8680       if( gameInfo.variant == VariantShogi )
8681            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8682       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8683            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8684       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8685                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8686            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8687       if( gameInfo.variant == VariantCourier )
8688            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8689       if( gameInfo.variant == VariantSuper )
8690            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8691       if( gameInfo.variant == VariantGreat )
8692            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8693
8694       if(overruled) {
8695            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8696                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8697            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8698            if(StrStr(cps->variants, b) == NULL) { 
8699                // specific sized variant not known, check if general sizing allowed
8700                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8701                    if(StrStr(cps->variants, "boardsize") == NULL) {
8702                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8703                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8704                        DisplayFatalError(buf, 0, 1);
8705                        return;
8706                    }
8707                    /* [HGM] here we really should compare with the maximum supported board size */
8708                }
8709            }
8710       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8711       sprintf(buf, "variant %s\n", b);
8712       SendToProgram(buf, cps);
8713     }
8714     currentlyInitializedVariant = gameInfo.variant;
8715
8716     /* [HGM] send opening position in FRC to first engine */
8717     if(setup) {
8718           SendToProgram("force\n", cps);
8719           SendBoard(cps, 0);
8720           /* engine is now in force mode! Set flag to wake it up after first move. */
8721           setboardSpoiledMachineBlack = 1;
8722     }
8723
8724     if (cps->sendICS) {
8725       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8726       SendToProgram(buf, cps);
8727     }
8728     cps->maybeThinking = FALSE;
8729     cps->offeredDraw = 0;
8730     if (!appData.icsActive) {
8731         SendTimeControl(cps, movesPerSession, timeControl,
8732                         timeIncrement, appData.searchDepth,
8733                         searchTime);
8734     }
8735     if (appData.showThinking 
8736         // [HGM] thinking: four options require thinking output to be sent
8737         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8738                                 ) {
8739         SendToProgram("post\n", cps);
8740     }
8741     SendToProgram("hard\n", cps);
8742     if (!appData.ponderNextMove) {
8743         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8744            it without being sure what state we are in first.  "hard"
8745            is not a toggle, so that one is OK.
8746          */
8747         SendToProgram("easy\n", cps);
8748     }
8749     if (cps->usePing) {
8750       sprintf(buf, "ping %d\n", ++cps->lastPing);
8751       SendToProgram(buf, cps);
8752     }
8753     cps->initDone = TRUE;
8754 }   
8755
8756
8757 void
8758 StartChessProgram(cps)
8759      ChessProgramState *cps;
8760 {
8761     char buf[MSG_SIZ];
8762     int err;
8763
8764     if (appData.noChessProgram) return;
8765     cps->initDone = FALSE;
8766
8767     if (strcmp(cps->host, "localhost") == 0) {
8768         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8769     } else if (*appData.remoteShell == NULLCHAR) {
8770         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8771     } else {
8772         if (*appData.remoteUser == NULLCHAR) {
8773           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8774                     cps->program);
8775         } else {
8776           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8777                     cps->host, appData.remoteUser, cps->program);
8778         }
8779         err = StartChildProcess(buf, "", &cps->pr);
8780     }
8781     
8782     if (err != 0) {
8783         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8784         DisplayFatalError(buf, err, 1);
8785         cps->pr = NoProc;
8786         cps->isr = NULL;
8787         return;
8788     }
8789     
8790     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8791     if (cps->protocolVersion > 1) {
8792       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8793       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8794       cps->comboCnt = 0;  //                and values of combo boxes
8795       SendToProgram(buf, cps);
8796     } else {
8797       SendToProgram("xboard\n", cps);
8798     }
8799 }
8800
8801
8802 void
8803 TwoMachinesEventIfReady P((void))
8804 {
8805   if (first.lastPing != first.lastPong) {
8806     DisplayMessage("", _("Waiting for first chess program"));
8807     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8808     return;
8809   }
8810   if (second.lastPing != second.lastPong) {
8811     DisplayMessage("", _("Waiting for second chess program"));
8812     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8813     return;
8814   }
8815   ThawUI();
8816   TwoMachinesEvent();
8817 }
8818
8819 void
8820 NextMatchGame P((void))
8821 {
8822     int index; /* [HGM] autoinc: step load index during match */
8823     Reset(FALSE, TRUE);
8824     if (*appData.loadGameFile != NULLCHAR) {
8825         index = appData.loadGameIndex;
8826         if(index < 0) { // [HGM] autoinc
8827             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8828             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8829         } 
8830         LoadGameFromFile(appData.loadGameFile,
8831                          index,
8832                          appData.loadGameFile, FALSE);
8833     } else if (*appData.loadPositionFile != NULLCHAR) {
8834         index = appData.loadPositionIndex;
8835         if(index < 0) { // [HGM] autoinc
8836             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8837             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8838         } 
8839         LoadPositionFromFile(appData.loadPositionFile,
8840                              index,
8841                              appData.loadPositionFile);
8842     }
8843     TwoMachinesEventIfReady();
8844 }
8845
8846 void UserAdjudicationEvent( int result )
8847 {
8848     ChessMove gameResult = GameIsDrawn;
8849
8850     if( result > 0 ) {
8851         gameResult = WhiteWins;
8852     }
8853     else if( result < 0 ) {
8854         gameResult = BlackWins;
8855     }
8856
8857     if( gameMode == TwoMachinesPlay ) {
8858         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8859     }
8860 }
8861
8862
8863 // [HGM] save: calculate checksum of game to make games easily identifiable
8864 int StringCheckSum(char *s)
8865 {
8866         int i = 0;
8867         if(s==NULL) return 0;
8868         while(*s) i = i*259 + *s++;
8869         return i;
8870 }
8871
8872 int GameCheckSum()
8873 {
8874         int i, sum=0;
8875         for(i=backwardMostMove; i<forwardMostMove; i++) {
8876                 sum += pvInfoList[i].depth;
8877                 sum += StringCheckSum(parseList[i]);
8878                 sum += StringCheckSum(commentList[i]);
8879                 sum *= 261;
8880         }
8881         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8882         return sum + StringCheckSum(commentList[i]);
8883 } // end of save patch
8884
8885 void
8886 GameEnds(result, resultDetails, whosays)
8887      ChessMove result;
8888      char *resultDetails;
8889      int whosays;
8890 {
8891     GameMode nextGameMode;
8892     int isIcsGame;
8893     char buf[MSG_SIZ];
8894
8895     if(endingGame) return; /* [HGM] crash: forbid recursion */
8896     endingGame = 1;
8897
8898     if (appData.debugMode) {
8899       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8900               result, resultDetails ? resultDetails : "(null)", whosays);
8901     }
8902
8903     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8904
8905     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8906         /* If we are playing on ICS, the server decides when the
8907            game is over, but the engine can offer to draw, claim 
8908            a draw, or resign. 
8909          */
8910 #if ZIPPY
8911         if (appData.zippyPlay && first.initDone) {
8912             if (result == GameIsDrawn) {
8913                 /* In case draw still needs to be claimed */
8914                 SendToICS(ics_prefix);
8915                 SendToICS("draw\n");
8916             } else if (StrCaseStr(resultDetails, "resign")) {
8917                 SendToICS(ics_prefix);
8918                 SendToICS("resign\n");
8919             }
8920         }
8921 #endif
8922         endingGame = 0; /* [HGM] crash */
8923         return;
8924     }
8925
8926     /* If we're loading the game from a file, stop */
8927     if (whosays == GE_FILE) {
8928       (void) StopLoadGameTimer();
8929       gameFileFP = NULL;
8930     }
8931
8932     /* Cancel draw offers */
8933     first.offeredDraw = second.offeredDraw = 0;
8934
8935     /* If this is an ICS game, only ICS can really say it's done;
8936        if not, anyone can. */
8937     isIcsGame = (gameMode == IcsPlayingWhite || 
8938                  gameMode == IcsPlayingBlack || 
8939                  gameMode == IcsObserving    || 
8940                  gameMode == IcsExamining);
8941
8942     if (!isIcsGame || whosays == GE_ICS) {
8943         /* OK -- not an ICS game, or ICS said it was done */
8944         StopClocks();
8945         if (!isIcsGame && !appData.noChessProgram) 
8946           SetUserThinkingEnables();
8947     
8948         /* [HGM] if a machine claims the game end we verify this claim */
8949         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8950             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8951                 char claimer;
8952                 ChessMove trueResult = (ChessMove) -1;
8953
8954                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8955                                             first.twoMachinesColor[0] :
8956                                             second.twoMachinesColor[0] ;
8957
8958                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8959                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8960                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8961                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8962                 } else
8963                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8964                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8965                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8966                 } else
8967                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8968                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8969                 }
8970
8971                 // now verify win claims, but not in drop games, as we don't understand those yet
8972                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8973                                                  || gameInfo.variant == VariantGreat) &&
8974                     (result == WhiteWins && claimer == 'w' ||
8975                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8976                       if (appData.debugMode) {
8977                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8978                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8979                       }
8980                       if(result != trueResult) {
8981                               sprintf(buf, "False win claim: '%s'", resultDetails);
8982                               result = claimer == 'w' ? BlackWins : WhiteWins;
8983                               resultDetails = buf;
8984                       }
8985                 } else
8986                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8987                     && (forwardMostMove <= backwardMostMove ||
8988                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8989                         (claimer=='b')==(forwardMostMove&1))
8990                                                                                   ) {
8991                       /* [HGM] verify: draws that were not flagged are false claims */
8992                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8993                       result = claimer == 'w' ? BlackWins : WhiteWins;
8994                       resultDetails = buf;
8995                 }
8996                 /* (Claiming a loss is accepted no questions asked!) */
8997             }
8998             /* [HGM] bare: don't allow bare King to win */
8999             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9000                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
9001                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9002                && result != GameIsDrawn)
9003             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9004                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9005                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9006                         if(p >= 0 && p <= (int)WhiteKing) k++;
9007                 }
9008                 if (appData.debugMode) {
9009                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9010                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9011                 }
9012                 if(k <= 1) {
9013                         result = GameIsDrawn;
9014                         sprintf(buf, "%s but bare king", resultDetails);
9015                         resultDetails = buf;
9016                 }
9017             }
9018         }
9019
9020
9021         if(serverMoves != NULL && !loadFlag) { char c = '=';
9022             if(result==WhiteWins) c = '+';
9023             if(result==BlackWins) c = '-';
9024             if(resultDetails != NULL)
9025                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9026         }
9027         if (resultDetails != NULL) {
9028             gameInfo.result = result;
9029             gameInfo.resultDetails = StrSave(resultDetails);
9030
9031             /* display last move only if game was not loaded from file */
9032             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9033                 DisplayMove(currentMove - 1);
9034     
9035             if (forwardMostMove != 0) {
9036                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9037                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9038                                                                 ) {
9039                     if (*appData.saveGameFile != NULLCHAR) {
9040                         SaveGameToFile(appData.saveGameFile, TRUE);
9041                     } else if (appData.autoSaveGames) {
9042                         AutoSaveGame();
9043                     }
9044                     if (*appData.savePositionFile != NULLCHAR) {
9045                         SavePositionToFile(appData.savePositionFile);
9046                     }
9047                 }
9048             }
9049
9050             /* Tell program how game ended in case it is learning */
9051             /* [HGM] Moved this to after saving the PGN, just in case */
9052             /* engine died and we got here through time loss. In that */
9053             /* case we will get a fatal error writing the pipe, which */
9054             /* would otherwise lose us the PGN.                       */
9055             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9056             /* output during GameEnds should never be fatal anymore   */
9057             if (gameMode == MachinePlaysWhite ||
9058                 gameMode == MachinePlaysBlack ||
9059                 gameMode == TwoMachinesPlay ||
9060                 gameMode == IcsPlayingWhite ||
9061                 gameMode == IcsPlayingBlack ||
9062                 gameMode == BeginningOfGame) {
9063                 char buf[MSG_SIZ];
9064                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9065                         resultDetails);
9066                 if (first.pr != NoProc) {
9067                     SendToProgram(buf, &first);
9068                 }
9069                 if (second.pr != NoProc &&
9070                     gameMode == TwoMachinesPlay) {
9071                     SendToProgram(buf, &second);
9072                 }
9073             }
9074         }
9075
9076         if (appData.icsActive) {
9077             if (appData.quietPlay &&
9078                 (gameMode == IcsPlayingWhite ||
9079                  gameMode == IcsPlayingBlack)) {
9080                 SendToICS(ics_prefix);
9081                 SendToICS("set shout 1\n");
9082             }
9083             nextGameMode = IcsIdle;
9084             ics_user_moved = FALSE;
9085             /* clean up premove.  It's ugly when the game has ended and the
9086              * premove highlights are still on the board.
9087              */
9088             if (gotPremove) {
9089               gotPremove = FALSE;
9090               ClearPremoveHighlights();
9091               DrawPosition(FALSE, boards[currentMove]);
9092             }
9093             if (whosays == GE_ICS) {
9094                 switch (result) {
9095                 case WhiteWins:
9096                     if (gameMode == IcsPlayingWhite)
9097                         PlayIcsWinSound();
9098                     else if(gameMode == IcsPlayingBlack)
9099                         PlayIcsLossSound();
9100                     break;
9101                 case BlackWins:
9102                     if (gameMode == IcsPlayingBlack)
9103                         PlayIcsWinSound();
9104                     else if(gameMode == IcsPlayingWhite)
9105                         PlayIcsLossSound();
9106                     break;
9107                 case GameIsDrawn:
9108                     PlayIcsDrawSound();
9109                     break;
9110                 default:
9111                     PlayIcsUnfinishedSound();
9112                 }
9113             }
9114         } else if (gameMode == EditGame ||
9115                    gameMode == PlayFromGameFile || 
9116                    gameMode == AnalyzeMode || 
9117                    gameMode == AnalyzeFile) {
9118             nextGameMode = gameMode;
9119         } else {
9120             nextGameMode = EndOfGame;
9121         }
9122         pausing = FALSE;
9123         ModeHighlight();
9124     } else {
9125         nextGameMode = gameMode;
9126     }
9127
9128     if (appData.noChessProgram) {
9129         gameMode = nextGameMode;
9130         ModeHighlight();
9131         endingGame = 0; /* [HGM] crash */
9132         return;
9133     }
9134
9135     if (first.reuse) {
9136         /* Put first chess program into idle state */
9137         if (first.pr != NoProc &&
9138             (gameMode == MachinePlaysWhite ||
9139              gameMode == MachinePlaysBlack ||
9140              gameMode == TwoMachinesPlay ||
9141              gameMode == IcsPlayingWhite ||
9142              gameMode == IcsPlayingBlack ||
9143              gameMode == BeginningOfGame)) {
9144             SendToProgram("force\n", &first);
9145             if (first.usePing) {
9146               char buf[MSG_SIZ];
9147               sprintf(buf, "ping %d\n", ++first.lastPing);
9148               SendToProgram(buf, &first);
9149             }
9150         }
9151     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9152         /* Kill off first chess program */
9153         if (first.isr != NULL)
9154           RemoveInputSource(first.isr);
9155         first.isr = NULL;
9156     
9157         if (first.pr != NoProc) {
9158             ExitAnalyzeMode();
9159             DoSleep( appData.delayBeforeQuit );
9160             SendToProgram("quit\n", &first);
9161             DoSleep( appData.delayAfterQuit );
9162             DestroyChildProcess(first.pr, first.useSigterm);
9163         }
9164         first.pr = NoProc;
9165     }
9166     if (second.reuse) {
9167         /* Put second chess program into idle state */
9168         if (second.pr != NoProc &&
9169             gameMode == TwoMachinesPlay) {
9170             SendToProgram("force\n", &second);
9171             if (second.usePing) {
9172               char buf[MSG_SIZ];
9173               sprintf(buf, "ping %d\n", ++second.lastPing);
9174               SendToProgram(buf, &second);
9175             }
9176         }
9177     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9178         /* Kill off second chess program */
9179         if (second.isr != NULL)
9180           RemoveInputSource(second.isr);
9181         second.isr = NULL;
9182     
9183         if (second.pr != NoProc) {
9184             DoSleep( appData.delayBeforeQuit );
9185             SendToProgram("quit\n", &second);
9186             DoSleep( appData.delayAfterQuit );
9187             DestroyChildProcess(second.pr, second.useSigterm);
9188         }
9189         second.pr = NoProc;
9190     }
9191
9192     if (matchMode && gameMode == TwoMachinesPlay) {
9193         switch (result) {
9194         case WhiteWins:
9195           if (first.twoMachinesColor[0] == 'w') {
9196             first.matchWins++;
9197           } else {
9198             second.matchWins++;
9199           }
9200           break;
9201         case BlackWins:
9202           if (first.twoMachinesColor[0] == 'b') {
9203             first.matchWins++;
9204           } else {
9205             second.matchWins++;
9206           }
9207           break;
9208         default:
9209           break;
9210         }
9211         if (matchGame < appData.matchGames) {
9212             char *tmp;
9213             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9214                 tmp = first.twoMachinesColor;
9215                 first.twoMachinesColor = second.twoMachinesColor;
9216                 second.twoMachinesColor = tmp;
9217             }
9218             gameMode = nextGameMode;
9219             matchGame++;
9220             if(appData.matchPause>10000 || appData.matchPause<10)
9221                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9222             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9223             endingGame = 0; /* [HGM] crash */
9224             return;
9225         } else {
9226             char buf[MSG_SIZ];
9227             gameMode = nextGameMode;
9228             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9229                     first.tidy, second.tidy,
9230                     first.matchWins, second.matchWins,
9231                     appData.matchGames - (first.matchWins + second.matchWins));
9232             DisplayFatalError(buf, 0, 0);
9233         }
9234     }
9235     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9236         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9237       ExitAnalyzeMode();
9238     gameMode = nextGameMode;
9239     ModeHighlight();
9240     endingGame = 0;  /* [HGM] crash */
9241 }
9242
9243 /* Assumes program was just initialized (initString sent).
9244    Leaves program in force mode. */
9245 void
9246 FeedMovesToProgram(cps, upto) 
9247      ChessProgramState *cps;
9248      int upto;
9249 {
9250     int i;
9251     
9252     if (appData.debugMode)
9253       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9254               startedFromSetupPosition ? "position and " : "",
9255               backwardMostMove, upto, cps->which);
9256     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9257         // [HGM] variantswitch: make engine aware of new variant
9258         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9259                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9260         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9261         SendToProgram(buf, cps);
9262         currentlyInitializedVariant = gameInfo.variant;
9263     }
9264     SendToProgram("force\n", cps);
9265     if (startedFromSetupPosition) {
9266         SendBoard(cps, backwardMostMove);
9267     if (appData.debugMode) {
9268         fprintf(debugFP, "feedMoves\n");
9269     }
9270     }
9271     for (i = backwardMostMove; i < upto; i++) {
9272         SendMoveToProgram(i, cps);
9273     }
9274 }
9275
9276
9277 void
9278 ResurrectChessProgram()
9279 {
9280      /* The chess program may have exited.
9281         If so, restart it and feed it all the moves made so far. */
9282
9283     if (appData.noChessProgram || first.pr != NoProc) return;
9284     
9285     StartChessProgram(&first);
9286     InitChessProgram(&first, FALSE);
9287     FeedMovesToProgram(&first, currentMove);
9288
9289     if (!first.sendTime) {
9290         /* can't tell gnuchess what its clock should read,
9291            so we bow to its notion. */
9292         ResetClocks();
9293         timeRemaining[0][currentMove] = whiteTimeRemaining;
9294         timeRemaining[1][currentMove] = blackTimeRemaining;
9295     }
9296
9297     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9298                 appData.icsEngineAnalyze) && first.analysisSupport) {
9299       SendToProgram("analyze\n", &first);
9300       first.analyzing = TRUE;
9301     }
9302 }
9303
9304 /*
9305  * Button procedures
9306  */
9307 void
9308 Reset(redraw, init)
9309      int redraw, init;
9310 {
9311     int i;
9312
9313     if (appData.debugMode) {
9314         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9315                 redraw, init, gameMode);
9316     }
9317     CleanupTail(); // [HGM] vari: delete any stored variations
9318     pausing = pauseExamInvalid = FALSE;
9319     startedFromSetupPosition = blackPlaysFirst = FALSE;
9320     firstMove = TRUE;
9321     whiteFlag = blackFlag = FALSE;
9322     userOfferedDraw = FALSE;
9323     hintRequested = bookRequested = FALSE;
9324     first.maybeThinking = FALSE;
9325     second.maybeThinking = FALSE;
9326     first.bookSuspend = FALSE; // [HGM] book
9327     second.bookSuspend = FALSE;
9328     thinkOutput[0] = NULLCHAR;
9329     lastHint[0] = NULLCHAR;
9330     ClearGameInfo(&gameInfo);
9331     gameInfo.variant = StringToVariant(appData.variant);
9332     ics_user_moved = ics_clock_paused = FALSE;
9333     ics_getting_history = H_FALSE;
9334     ics_gamenum = -1;
9335     white_holding[0] = black_holding[0] = NULLCHAR;
9336     ClearProgramStats();
9337     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9338     
9339     ResetFrontEnd();
9340     ClearHighlights();
9341     flipView = appData.flipView;
9342     ClearPremoveHighlights();
9343     gotPremove = FALSE;
9344     alarmSounded = FALSE;
9345
9346     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9347     if(appData.serverMovesName != NULL) {
9348         /* [HGM] prepare to make moves file for broadcasting */
9349         clock_t t = clock();
9350         if(serverMoves != NULL) fclose(serverMoves);
9351         serverMoves = fopen(appData.serverMovesName, "r");
9352         if(serverMoves != NULL) {
9353             fclose(serverMoves);
9354             /* delay 15 sec before overwriting, so all clients can see end */
9355             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9356         }
9357         serverMoves = fopen(appData.serverMovesName, "w");
9358     }
9359
9360     ExitAnalyzeMode();
9361     gameMode = BeginningOfGame;
9362     ModeHighlight();
9363     if(appData.icsActive) gameInfo.variant = VariantNormal;
9364     currentMove = forwardMostMove = backwardMostMove = 0;
9365     InitPosition(redraw);
9366     for (i = 0; i < MAX_MOVES; i++) {
9367         if (commentList[i] != NULL) {
9368             free(commentList[i]);
9369             commentList[i] = NULL;
9370         }
9371     }
9372     ResetClocks();
9373     timeRemaining[0][0] = whiteTimeRemaining;
9374     timeRemaining[1][0] = blackTimeRemaining;
9375     if (first.pr == NULL) {
9376         StartChessProgram(&first);
9377     }
9378     if (init) {
9379             InitChessProgram(&first, startedFromSetupPosition);
9380     }
9381     DisplayTitle("");
9382     DisplayMessage("", "");
9383     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9384     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9385 }
9386
9387 void
9388 AutoPlayGameLoop()
9389 {
9390     for (;;) {
9391         if (!AutoPlayOneMove())
9392           return;
9393         if (matchMode || appData.timeDelay == 0)
9394           continue;
9395         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9396           return;
9397         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9398         break;
9399     }
9400 }
9401
9402
9403 int
9404 AutoPlayOneMove()
9405 {
9406     int fromX, fromY, toX, toY;
9407
9408     if (appData.debugMode) {
9409       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9410     }
9411
9412     if (gameMode != PlayFromGameFile)
9413       return FALSE;
9414
9415     if (currentMove >= forwardMostMove) {
9416       gameMode = EditGame;
9417       ModeHighlight();
9418
9419       /* [AS] Clear current move marker at the end of a game */
9420       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9421
9422       return FALSE;
9423     }
9424     
9425     toX = moveList[currentMove][2] - AAA;
9426     toY = moveList[currentMove][3] - ONE;
9427
9428     if (moveList[currentMove][1] == '@') {
9429         if (appData.highlightLastMove) {
9430             SetHighlights(-1, -1, toX, toY);
9431         }
9432     } else {
9433         fromX = moveList[currentMove][0] - AAA;
9434         fromY = moveList[currentMove][1] - ONE;
9435
9436         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9437
9438         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9439
9440         if (appData.highlightLastMove) {
9441             SetHighlights(fromX, fromY, toX, toY);
9442         }
9443     }
9444     DisplayMove(currentMove);
9445     SendMoveToProgram(currentMove++, &first);
9446     DisplayBothClocks();
9447     DrawPosition(FALSE, boards[currentMove]);
9448     // [HGM] PV info: always display, routine tests if empty
9449     DisplayComment(currentMove - 1, commentList[currentMove]);
9450     return TRUE;
9451 }
9452
9453
9454 int
9455 LoadGameOneMove(readAhead)
9456      ChessMove readAhead;
9457 {
9458     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9459     char promoChar = NULLCHAR;
9460     ChessMove moveType;
9461     char move[MSG_SIZ];
9462     char *p, *q;
9463     
9464     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9465         gameMode != AnalyzeMode && gameMode != Training) {
9466         gameFileFP = NULL;
9467         return FALSE;
9468     }
9469     
9470     yyboardindex = forwardMostMove;
9471     if (readAhead != (ChessMove)0) {
9472       moveType = readAhead;
9473     } else {
9474       if (gameFileFP == NULL)
9475           return FALSE;
9476       moveType = (ChessMove) yylex();
9477     }
9478     
9479     done = FALSE;
9480     switch (moveType) {
9481       case Comment:
9482         if (appData.debugMode) 
9483           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9484         p = yy_text;
9485
9486         /* append the comment but don't display it */
9487         AppendComment(currentMove, p, FALSE);
9488         return TRUE;
9489
9490       case WhiteCapturesEnPassant:
9491       case BlackCapturesEnPassant:
9492       case WhitePromotionChancellor:
9493       case BlackPromotionChancellor:
9494       case WhitePromotionArchbishop:
9495       case BlackPromotionArchbishop:
9496       case WhitePromotionCentaur:
9497       case BlackPromotionCentaur:
9498       case WhitePromotionQueen:
9499       case BlackPromotionQueen:
9500       case WhitePromotionRook:
9501       case BlackPromotionRook:
9502       case WhitePromotionBishop:
9503       case BlackPromotionBishop:
9504       case WhitePromotionKnight:
9505       case BlackPromotionKnight:
9506       case WhitePromotionKing:
9507       case BlackPromotionKing:
9508       case NormalMove:
9509       case WhiteKingSideCastle:
9510       case WhiteQueenSideCastle:
9511       case BlackKingSideCastle:
9512       case BlackQueenSideCastle:
9513       case WhiteKingSideCastleWild:
9514       case WhiteQueenSideCastleWild:
9515       case BlackKingSideCastleWild:
9516       case BlackQueenSideCastleWild:
9517       /* PUSH Fabien */
9518       case WhiteHSideCastleFR:
9519       case WhiteASideCastleFR:
9520       case BlackHSideCastleFR:
9521       case BlackASideCastleFR:
9522       /* POP Fabien */
9523         if (appData.debugMode)
9524           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9525         fromX = currentMoveString[0] - AAA;
9526         fromY = currentMoveString[1] - ONE;
9527         toX = currentMoveString[2] - AAA;
9528         toY = currentMoveString[3] - ONE;
9529         promoChar = currentMoveString[4];
9530         break;
9531
9532       case WhiteDrop:
9533       case BlackDrop:
9534         if (appData.debugMode)
9535           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9536         fromX = moveType == WhiteDrop ?
9537           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9538         (int) CharToPiece(ToLower(currentMoveString[0]));
9539         fromY = DROP_RANK;
9540         toX = currentMoveString[2] - AAA;
9541         toY = currentMoveString[3] - ONE;
9542         break;
9543
9544       case WhiteWins:
9545       case BlackWins:
9546       case GameIsDrawn:
9547       case GameUnfinished:
9548         if (appData.debugMode)
9549           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9550         p = strchr(yy_text, '{');
9551         if (p == NULL) p = strchr(yy_text, '(');
9552         if (p == NULL) {
9553             p = yy_text;
9554             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9555         } else {
9556             q = strchr(p, *p == '{' ? '}' : ')');
9557             if (q != NULL) *q = NULLCHAR;
9558             p++;
9559         }
9560         GameEnds(moveType, p, GE_FILE);
9561         done = TRUE;
9562         if (cmailMsgLoaded) {
9563             ClearHighlights();
9564             flipView = WhiteOnMove(currentMove);
9565             if (moveType == GameUnfinished) flipView = !flipView;
9566             if (appData.debugMode)
9567               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9568         }
9569         break;
9570
9571       case (ChessMove) 0:       /* end of file */
9572         if (appData.debugMode)
9573           fprintf(debugFP, "Parser hit end of file\n");
9574         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9575           case MT_NONE:
9576           case MT_CHECK:
9577             break;
9578           case MT_CHECKMATE:
9579           case MT_STAINMATE:
9580             if (WhiteOnMove(currentMove)) {
9581                 GameEnds(BlackWins, "Black mates", GE_FILE);
9582             } else {
9583                 GameEnds(WhiteWins, "White mates", GE_FILE);
9584             }
9585             break;
9586           case MT_STALEMATE:
9587             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9588             break;
9589         }
9590         done = TRUE;
9591         break;
9592
9593       case MoveNumberOne:
9594         if (lastLoadGameStart == GNUChessGame) {
9595             /* GNUChessGames have numbers, but they aren't move numbers */
9596             if (appData.debugMode)
9597               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9598                       yy_text, (int) moveType);
9599             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9600         }
9601         /* else fall thru */
9602
9603       case XBoardGame:
9604       case GNUChessGame:
9605       case PGNTag:
9606         /* Reached start of next game in file */
9607         if (appData.debugMode)
9608           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9609         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9610           case MT_NONE:
9611           case MT_CHECK:
9612             break;
9613           case MT_CHECKMATE:
9614           case MT_STAINMATE:
9615             if (WhiteOnMove(currentMove)) {
9616                 GameEnds(BlackWins, "Black mates", GE_FILE);
9617             } else {
9618                 GameEnds(WhiteWins, "White mates", GE_FILE);
9619             }
9620             break;
9621           case MT_STALEMATE:
9622             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9623             break;
9624         }
9625         done = TRUE;
9626         break;
9627
9628       case PositionDiagram:     /* should not happen; ignore */
9629       case ElapsedTime:         /* ignore */
9630       case NAG:                 /* ignore */
9631         if (appData.debugMode)
9632           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9633                   yy_text, (int) moveType);
9634         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9635
9636       case IllegalMove:
9637         if (appData.testLegality) {
9638             if (appData.debugMode)
9639               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9640             sprintf(move, _("Illegal move: %d.%s%s"),
9641                     (forwardMostMove / 2) + 1,
9642                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9643             DisplayError(move, 0);
9644             done = TRUE;
9645         } else {
9646             if (appData.debugMode)
9647               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9648                       yy_text, currentMoveString);
9649             fromX = currentMoveString[0] - AAA;
9650             fromY = currentMoveString[1] - ONE;
9651             toX = currentMoveString[2] - AAA;
9652             toY = currentMoveString[3] - ONE;
9653             promoChar = currentMoveString[4];
9654         }
9655         break;
9656
9657       case AmbiguousMove:
9658         if (appData.debugMode)
9659           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9660         sprintf(move, _("Ambiguous move: %d.%s%s"),
9661                 (forwardMostMove / 2) + 1,
9662                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9663         DisplayError(move, 0);
9664         done = TRUE;
9665         break;
9666
9667       default:
9668       case ImpossibleMove:
9669         if (appData.debugMode)
9670           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9671         sprintf(move, _("Illegal move: %d.%s%s"),
9672                 (forwardMostMove / 2) + 1,
9673                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9674         DisplayError(move, 0);
9675         done = TRUE;
9676         break;
9677     }
9678
9679     if (done) {
9680         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9681             DrawPosition(FALSE, boards[currentMove]);
9682             DisplayBothClocks();
9683             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9684               DisplayComment(currentMove - 1, commentList[currentMove]);
9685         }
9686         (void) StopLoadGameTimer();
9687         gameFileFP = NULL;
9688         cmailOldMove = forwardMostMove;
9689         return FALSE;
9690     } else {
9691         /* currentMoveString is set as a side-effect of yylex */
9692         strcat(currentMoveString, "\n");
9693         strcpy(moveList[forwardMostMove], currentMoveString);
9694         
9695         thinkOutput[0] = NULLCHAR;
9696         MakeMove(fromX, fromY, toX, toY, promoChar);
9697         currentMove = forwardMostMove;
9698         return TRUE;
9699     }
9700 }
9701
9702 /* Load the nth game from the given file */
9703 int
9704 LoadGameFromFile(filename, n, title, useList)
9705      char *filename;
9706      int n;
9707      char *title;
9708      /*Boolean*/ int useList;
9709 {
9710     FILE *f;
9711     char buf[MSG_SIZ];
9712
9713     if (strcmp(filename, "-") == 0) {
9714         f = stdin;
9715         title = "stdin";
9716     } else {
9717         f = fopen(filename, "rb");
9718         if (f == NULL) {
9719           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9720             DisplayError(buf, errno);
9721             return FALSE;
9722         }
9723     }
9724     if (fseek(f, 0, 0) == -1) {
9725         /* f is not seekable; probably a pipe */
9726         useList = FALSE;
9727     }
9728     if (useList && n == 0) {
9729         int error = GameListBuild(f);
9730         if (error) {
9731             DisplayError(_("Cannot build game list"), error);
9732         } else if (!ListEmpty(&gameList) &&
9733                    ((ListGame *) gameList.tailPred)->number > 1) {
9734             GameListPopUp(f, title);
9735             return TRUE;
9736         }
9737         GameListDestroy();
9738         n = 1;
9739     }
9740     if (n == 0) n = 1;
9741     return LoadGame(f, n, title, FALSE);
9742 }
9743
9744
9745 void
9746 MakeRegisteredMove()
9747 {
9748     int fromX, fromY, toX, toY;
9749     char promoChar;
9750     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9751         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9752           case CMAIL_MOVE:
9753           case CMAIL_DRAW:
9754             if (appData.debugMode)
9755               fprintf(debugFP, "Restoring %s for game %d\n",
9756                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9757     
9758             thinkOutput[0] = NULLCHAR;
9759             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9760             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9761             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9762             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9763             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9764             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9765             MakeMove(fromX, fromY, toX, toY, promoChar);
9766             ShowMove(fromX, fromY, toX, toY);
9767               
9768             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9769               case MT_NONE:
9770               case MT_CHECK:
9771                 break;
9772                 
9773               case MT_CHECKMATE:
9774               case MT_STAINMATE:
9775                 if (WhiteOnMove(currentMove)) {
9776                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9777                 } else {
9778                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9779                 }
9780                 break;
9781                 
9782               case MT_STALEMATE:
9783                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9784                 break;
9785             }
9786
9787             break;
9788             
9789           case CMAIL_RESIGN:
9790             if (WhiteOnMove(currentMove)) {
9791                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9792             } else {
9793                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9794             }
9795             break;
9796             
9797           case CMAIL_ACCEPT:
9798             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9799             break;
9800               
9801           default:
9802             break;
9803         }
9804     }
9805
9806     return;
9807 }
9808
9809 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9810 int
9811 CmailLoadGame(f, gameNumber, title, useList)
9812      FILE *f;
9813      int gameNumber;
9814      char *title;
9815      int useList;
9816 {
9817     int retVal;
9818
9819     if (gameNumber > nCmailGames) {
9820         DisplayError(_("No more games in this message"), 0);
9821         return FALSE;
9822     }
9823     if (f == lastLoadGameFP) {
9824         int offset = gameNumber - lastLoadGameNumber;
9825         if (offset == 0) {
9826             cmailMsg[0] = NULLCHAR;
9827             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9828                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9829                 nCmailMovesRegistered--;
9830             }
9831             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9832             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9833                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9834             }
9835         } else {
9836             if (! RegisterMove()) return FALSE;
9837         }
9838     }
9839
9840     retVal = LoadGame(f, gameNumber, title, useList);
9841
9842     /* Make move registered during previous look at this game, if any */
9843     MakeRegisteredMove();
9844
9845     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9846         commentList[currentMove]
9847           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9848         DisplayComment(currentMove - 1, commentList[currentMove]);
9849     }
9850
9851     return retVal;
9852 }
9853
9854 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9855 int
9856 ReloadGame(offset)
9857      int offset;
9858 {
9859     int gameNumber = lastLoadGameNumber + offset;
9860     if (lastLoadGameFP == NULL) {
9861         DisplayError(_("No game has been loaded yet"), 0);
9862         return FALSE;
9863     }
9864     if (gameNumber <= 0) {
9865         DisplayError(_("Can't back up any further"), 0);
9866         return FALSE;
9867     }
9868     if (cmailMsgLoaded) {
9869         return CmailLoadGame(lastLoadGameFP, gameNumber,
9870                              lastLoadGameTitle, lastLoadGameUseList);
9871     } else {
9872         return LoadGame(lastLoadGameFP, gameNumber,
9873                         lastLoadGameTitle, lastLoadGameUseList);
9874     }
9875 }
9876
9877
9878
9879 /* Load the nth game from open file f */
9880 int
9881 LoadGame(f, gameNumber, title, useList)
9882      FILE *f;
9883      int gameNumber;
9884      char *title;
9885      int useList;
9886 {
9887     ChessMove cm;
9888     char buf[MSG_SIZ];
9889     int gn = gameNumber;
9890     ListGame *lg = NULL;
9891     int numPGNTags = 0;
9892     int err;
9893     GameMode oldGameMode;
9894     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9895
9896     if (appData.debugMode) 
9897         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9898
9899     if (gameMode == Training )
9900         SetTrainingModeOff();
9901
9902     oldGameMode = gameMode;
9903     if (gameMode != BeginningOfGame) {
9904       Reset(FALSE, TRUE);
9905     }
9906
9907     gameFileFP = f;
9908     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9909         fclose(lastLoadGameFP);
9910     }
9911
9912     if (useList) {
9913         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9914         
9915         if (lg) {
9916             fseek(f, lg->offset, 0);
9917             GameListHighlight(gameNumber);
9918             gn = 1;
9919         }
9920         else {
9921             DisplayError(_("Game number out of range"), 0);
9922             return FALSE;
9923         }
9924     } else {
9925         GameListDestroy();
9926         if (fseek(f, 0, 0) == -1) {
9927             if (f == lastLoadGameFP ?
9928                 gameNumber == lastLoadGameNumber + 1 :
9929                 gameNumber == 1) {
9930                 gn = 1;
9931             } else {
9932                 DisplayError(_("Can't seek on game file"), 0);
9933                 return FALSE;
9934             }
9935         }
9936     }
9937     lastLoadGameFP = f;
9938     lastLoadGameNumber = gameNumber;
9939     strcpy(lastLoadGameTitle, title);
9940     lastLoadGameUseList = useList;
9941
9942     yynewfile(f);
9943
9944     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9945       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9946                 lg->gameInfo.black);
9947             DisplayTitle(buf);
9948     } else if (*title != NULLCHAR) {
9949         if (gameNumber > 1) {
9950             sprintf(buf, "%s %d", title, gameNumber);
9951             DisplayTitle(buf);
9952         } else {
9953             DisplayTitle(title);
9954         }
9955     }
9956
9957     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9958         gameMode = PlayFromGameFile;
9959         ModeHighlight();
9960     }
9961
9962     currentMove = forwardMostMove = backwardMostMove = 0;
9963     CopyBoard(boards[0], initialPosition);
9964     StopClocks();
9965
9966     /*
9967      * Skip the first gn-1 games in the file.
9968      * Also skip over anything that precedes an identifiable 
9969      * start of game marker, to avoid being confused by 
9970      * garbage at the start of the file.  Currently 
9971      * recognized start of game markers are the move number "1",
9972      * the pattern "gnuchess .* game", the pattern
9973      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9974      * A game that starts with one of the latter two patterns
9975      * will also have a move number 1, possibly
9976      * following a position diagram.
9977      * 5-4-02: Let's try being more lenient and allowing a game to
9978      * start with an unnumbered move.  Does that break anything?
9979      */
9980     cm = lastLoadGameStart = (ChessMove) 0;
9981     while (gn > 0) {
9982         yyboardindex = forwardMostMove;
9983         cm = (ChessMove) yylex();
9984         switch (cm) {
9985           case (ChessMove) 0:
9986             if (cmailMsgLoaded) {
9987                 nCmailGames = CMAIL_MAX_GAMES - gn;
9988             } else {
9989                 Reset(TRUE, TRUE);
9990                 DisplayError(_("Game not found in file"), 0);
9991             }
9992             return FALSE;
9993
9994           case GNUChessGame:
9995           case XBoardGame:
9996             gn--;
9997             lastLoadGameStart = cm;
9998             break;
9999             
10000           case MoveNumberOne:
10001             switch (lastLoadGameStart) {
10002               case GNUChessGame:
10003               case XBoardGame:
10004               case PGNTag:
10005                 break;
10006               case MoveNumberOne:
10007               case (ChessMove) 0:
10008                 gn--;           /* count this game */
10009                 lastLoadGameStart = cm;
10010                 break;
10011               default:
10012                 /* impossible */
10013                 break;
10014             }
10015             break;
10016
10017           case PGNTag:
10018             switch (lastLoadGameStart) {
10019               case GNUChessGame:
10020               case PGNTag:
10021               case MoveNumberOne:
10022               case (ChessMove) 0:
10023                 gn--;           /* count this game */
10024                 lastLoadGameStart = cm;
10025                 break;
10026               case XBoardGame:
10027                 lastLoadGameStart = cm; /* game counted already */
10028                 break;
10029               default:
10030                 /* impossible */
10031                 break;
10032             }
10033             if (gn > 0) {
10034                 do {
10035                     yyboardindex = forwardMostMove;
10036                     cm = (ChessMove) yylex();
10037                 } while (cm == PGNTag || cm == Comment);
10038             }
10039             break;
10040
10041           case WhiteWins:
10042           case BlackWins:
10043           case GameIsDrawn:
10044             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10045                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10046                     != CMAIL_OLD_RESULT) {
10047                     nCmailResults ++ ;
10048                     cmailResult[  CMAIL_MAX_GAMES
10049                                 - gn - 1] = CMAIL_OLD_RESULT;
10050                 }
10051             }
10052             break;
10053
10054           case NormalMove:
10055             /* Only a NormalMove can be at the start of a game
10056              * without a position diagram. */
10057             if (lastLoadGameStart == (ChessMove) 0) {
10058               gn--;
10059               lastLoadGameStart = MoveNumberOne;
10060             }
10061             break;
10062
10063           default:
10064             break;
10065         }
10066     }
10067     
10068     if (appData.debugMode)
10069       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10070
10071     if (cm == XBoardGame) {
10072         /* Skip any header junk before position diagram and/or move 1 */
10073         for (;;) {
10074             yyboardindex = forwardMostMove;
10075             cm = (ChessMove) yylex();
10076
10077             if (cm == (ChessMove) 0 ||
10078                 cm == GNUChessGame || cm == XBoardGame) {
10079                 /* Empty game; pretend end-of-file and handle later */
10080                 cm = (ChessMove) 0;
10081                 break;
10082             }
10083
10084             if (cm == MoveNumberOne || cm == PositionDiagram ||
10085                 cm == PGNTag || cm == Comment)
10086               break;
10087         }
10088     } else if (cm == GNUChessGame) {
10089         if (gameInfo.event != NULL) {
10090             free(gameInfo.event);
10091         }
10092         gameInfo.event = StrSave(yy_text);
10093     }   
10094
10095     startedFromSetupPosition = FALSE;
10096     while (cm == PGNTag) {
10097         if (appData.debugMode) 
10098           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10099         err = ParsePGNTag(yy_text, &gameInfo);
10100         if (!err) numPGNTags++;
10101
10102         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10103         if(gameInfo.variant != oldVariant) {
10104             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10105             InitPosition(TRUE);
10106             oldVariant = gameInfo.variant;
10107             if (appData.debugMode) 
10108               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10109         }
10110
10111
10112         if (gameInfo.fen != NULL) {
10113           Board initial_position;
10114           startedFromSetupPosition = TRUE;
10115           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10116             Reset(TRUE, TRUE);
10117             DisplayError(_("Bad FEN position in file"), 0);
10118             return FALSE;
10119           }
10120           CopyBoard(boards[0], initial_position);
10121           if (blackPlaysFirst) {
10122             currentMove = forwardMostMove = backwardMostMove = 1;
10123             CopyBoard(boards[1], initial_position);
10124             strcpy(moveList[0], "");
10125             strcpy(parseList[0], "");
10126             timeRemaining[0][1] = whiteTimeRemaining;
10127             timeRemaining[1][1] = blackTimeRemaining;
10128             if (commentList[0] != NULL) {
10129               commentList[1] = commentList[0];
10130               commentList[0] = NULL;
10131             }
10132           } else {
10133             currentMove = forwardMostMove = backwardMostMove = 0;
10134           }
10135           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10136           {   int i;
10137               initialRulePlies = FENrulePlies;
10138               for( i=0; i< nrCastlingRights; i++ )
10139                   initialRights[i] = initial_position[CASTLING][i];
10140           }
10141           yyboardindex = forwardMostMove;
10142           free(gameInfo.fen);
10143           gameInfo.fen = NULL;
10144         }
10145
10146         yyboardindex = forwardMostMove;
10147         cm = (ChessMove) yylex();
10148
10149         /* Handle comments interspersed among the tags */
10150         while (cm == Comment) {
10151             char *p;
10152             if (appData.debugMode) 
10153               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10154             p = yy_text;
10155             AppendComment(currentMove, p, FALSE);
10156             yyboardindex = forwardMostMove;
10157             cm = (ChessMove) yylex();
10158         }
10159     }
10160
10161     /* don't rely on existence of Event tag since if game was
10162      * pasted from clipboard the Event tag may not exist
10163      */
10164     if (numPGNTags > 0){
10165         char *tags;
10166         if (gameInfo.variant == VariantNormal) {
10167           gameInfo.variant = StringToVariant(gameInfo.event);
10168         }
10169         if (!matchMode) {
10170           if( appData.autoDisplayTags ) {
10171             tags = PGNTags(&gameInfo);
10172             TagsPopUp(tags, CmailMsg());
10173             free(tags);
10174           }
10175         }
10176     } else {
10177         /* Make something up, but don't display it now */
10178         SetGameInfo();
10179         TagsPopDown();
10180     }
10181
10182     if (cm == PositionDiagram) {
10183         int i, j;
10184         char *p;
10185         Board initial_position;
10186
10187         if (appData.debugMode)
10188           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10189
10190         if (!startedFromSetupPosition) {
10191             p = yy_text;
10192             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10193               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10194                 switch (*p) {
10195                   case '[':
10196                   case '-':
10197                   case ' ':
10198                   case '\t':
10199                   case '\n':
10200                   case '\r':
10201                     break;
10202                   default:
10203                     initial_position[i][j++] = CharToPiece(*p);
10204                     break;
10205                 }
10206             while (*p == ' ' || *p == '\t' ||
10207                    *p == '\n' || *p == '\r') p++;
10208         
10209             if (strncmp(p, "black", strlen("black"))==0)
10210               blackPlaysFirst = TRUE;
10211             else
10212               blackPlaysFirst = FALSE;
10213             startedFromSetupPosition = TRUE;
10214         
10215             CopyBoard(boards[0], initial_position);
10216             if (blackPlaysFirst) {
10217                 currentMove = forwardMostMove = backwardMostMove = 1;
10218                 CopyBoard(boards[1], initial_position);
10219                 strcpy(moveList[0], "");
10220                 strcpy(parseList[0], "");
10221                 timeRemaining[0][1] = whiteTimeRemaining;
10222                 timeRemaining[1][1] = blackTimeRemaining;
10223                 if (commentList[0] != NULL) {
10224                     commentList[1] = commentList[0];
10225                     commentList[0] = NULL;
10226                 }
10227             } else {
10228                 currentMove = forwardMostMove = backwardMostMove = 0;
10229             }
10230         }
10231         yyboardindex = forwardMostMove;
10232         cm = (ChessMove) yylex();
10233     }
10234
10235     if (first.pr == NoProc) {
10236         StartChessProgram(&first);
10237     }
10238     InitChessProgram(&first, FALSE);
10239     SendToProgram("force\n", &first);
10240     if (startedFromSetupPosition) {
10241         SendBoard(&first, forwardMostMove);
10242     if (appData.debugMode) {
10243         fprintf(debugFP, "Load Game\n");
10244     }
10245         DisplayBothClocks();
10246     }      
10247
10248     /* [HGM] server: flag to write setup moves in broadcast file as one */
10249     loadFlag = appData.suppressLoadMoves;
10250
10251     while (cm == Comment) {
10252         char *p;
10253         if (appData.debugMode) 
10254           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10255         p = yy_text;
10256         AppendComment(currentMove, p, FALSE);
10257         yyboardindex = forwardMostMove;
10258         cm = (ChessMove) yylex();
10259     }
10260
10261     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10262         cm == WhiteWins || cm == BlackWins ||
10263         cm == GameIsDrawn || cm == GameUnfinished) {
10264         DisplayMessage("", _("No moves in game"));
10265         if (cmailMsgLoaded) {
10266             if (appData.debugMode)
10267               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10268             ClearHighlights();
10269             flipView = FALSE;
10270         }
10271         DrawPosition(FALSE, boards[currentMove]);
10272         DisplayBothClocks();
10273         gameMode = EditGame;
10274         ModeHighlight();
10275         gameFileFP = NULL;
10276         cmailOldMove = 0;
10277         return TRUE;
10278     }
10279
10280     // [HGM] PV info: routine tests if comment empty
10281     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10282         DisplayComment(currentMove - 1, commentList[currentMove]);
10283     }
10284     if (!matchMode && appData.timeDelay != 0) 
10285       DrawPosition(FALSE, boards[currentMove]);
10286
10287     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10288       programStats.ok_to_send = 1;
10289     }
10290
10291     /* if the first token after the PGN tags is a move
10292      * and not move number 1, retrieve it from the parser 
10293      */
10294     if (cm != MoveNumberOne)
10295         LoadGameOneMove(cm);
10296
10297     /* load the remaining moves from the file */
10298     while (LoadGameOneMove((ChessMove)0)) {
10299       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10300       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10301     }
10302
10303     /* rewind to the start of the game */
10304     currentMove = backwardMostMove;
10305
10306     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10307
10308     if (oldGameMode == AnalyzeFile ||
10309         oldGameMode == AnalyzeMode) {
10310       AnalyzeFileEvent();
10311     }
10312
10313     if (matchMode || appData.timeDelay == 0) {
10314       ToEndEvent();
10315       gameMode = EditGame;
10316       ModeHighlight();
10317     } else if (appData.timeDelay > 0) {
10318       AutoPlayGameLoop();
10319     }
10320
10321     if (appData.debugMode) 
10322         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10323
10324     loadFlag = 0; /* [HGM] true game starts */
10325     return TRUE;
10326 }
10327
10328 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10329 int
10330 ReloadPosition(offset)
10331      int offset;
10332 {
10333     int positionNumber = lastLoadPositionNumber + offset;
10334     if (lastLoadPositionFP == NULL) {
10335         DisplayError(_("No position has been loaded yet"), 0);
10336         return FALSE;
10337     }
10338     if (positionNumber <= 0) {
10339         DisplayError(_("Can't back up any further"), 0);
10340         return FALSE;
10341     }
10342     return LoadPosition(lastLoadPositionFP, positionNumber,
10343                         lastLoadPositionTitle);
10344 }
10345
10346 /* Load the nth position from the given file */
10347 int
10348 LoadPositionFromFile(filename, n, title)
10349      char *filename;
10350      int n;
10351      char *title;
10352 {
10353     FILE *f;
10354     char buf[MSG_SIZ];
10355
10356     if (strcmp(filename, "-") == 0) {
10357         return LoadPosition(stdin, n, "stdin");
10358     } else {
10359         f = fopen(filename, "rb");
10360         if (f == NULL) {
10361             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10362             DisplayError(buf, errno);
10363             return FALSE;
10364         } else {
10365             return LoadPosition(f, n, title);
10366         }
10367     }
10368 }
10369
10370 /* Load the nth position from the given open file, and close it */
10371 int
10372 LoadPosition(f, positionNumber, title)
10373      FILE *f;
10374      int positionNumber;
10375      char *title;
10376 {
10377     char *p, line[MSG_SIZ];
10378     Board initial_position;
10379     int i, j, fenMode, pn;
10380     
10381     if (gameMode == Training )
10382         SetTrainingModeOff();
10383
10384     if (gameMode != BeginningOfGame) {
10385         Reset(FALSE, TRUE);
10386     }
10387     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10388         fclose(lastLoadPositionFP);
10389     }
10390     if (positionNumber == 0) positionNumber = 1;
10391     lastLoadPositionFP = f;
10392     lastLoadPositionNumber = positionNumber;
10393     strcpy(lastLoadPositionTitle, title);
10394     if (first.pr == NoProc) {
10395       StartChessProgram(&first);
10396       InitChessProgram(&first, FALSE);
10397     }    
10398     pn = positionNumber;
10399     if (positionNumber < 0) {
10400         /* Negative position number means to seek to that byte offset */
10401         if (fseek(f, -positionNumber, 0) == -1) {
10402             DisplayError(_("Can't seek on position file"), 0);
10403             return FALSE;
10404         };
10405         pn = 1;
10406     } else {
10407         if (fseek(f, 0, 0) == -1) {
10408             if (f == lastLoadPositionFP ?
10409                 positionNumber == lastLoadPositionNumber + 1 :
10410                 positionNumber == 1) {
10411                 pn = 1;
10412             } else {
10413                 DisplayError(_("Can't seek on position file"), 0);
10414                 return FALSE;
10415             }
10416         }
10417     }
10418     /* See if this file is FEN or old-style xboard */
10419     if (fgets(line, MSG_SIZ, f) == NULL) {
10420         DisplayError(_("Position not found in file"), 0);
10421         return FALSE;
10422     }
10423     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10424     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10425
10426     if (pn >= 2) {
10427         if (fenMode || line[0] == '#') pn--;
10428         while (pn > 0) {
10429             /* skip positions before number pn */
10430             if (fgets(line, MSG_SIZ, f) == NULL) {
10431                 Reset(TRUE, TRUE);
10432                 DisplayError(_("Position not found in file"), 0);
10433                 return FALSE;
10434             }
10435             if (fenMode || line[0] == '#') pn--;
10436         }
10437     }
10438
10439     if (fenMode) {
10440         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10441             DisplayError(_("Bad FEN position in file"), 0);
10442             return FALSE;
10443         }
10444     } else {
10445         (void) fgets(line, MSG_SIZ, f);
10446         (void) fgets(line, MSG_SIZ, f);
10447     
10448         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10449             (void) fgets(line, MSG_SIZ, f);
10450             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10451                 if (*p == ' ')
10452                   continue;
10453                 initial_position[i][j++] = CharToPiece(*p);
10454             }
10455         }
10456     
10457         blackPlaysFirst = FALSE;
10458         if (!feof(f)) {
10459             (void) fgets(line, MSG_SIZ, f);
10460             if (strncmp(line, "black", strlen("black"))==0)
10461               blackPlaysFirst = TRUE;
10462         }
10463     }
10464     startedFromSetupPosition = TRUE;
10465     
10466     SendToProgram("force\n", &first);
10467     CopyBoard(boards[0], initial_position);
10468     if (blackPlaysFirst) {
10469         currentMove = forwardMostMove = backwardMostMove = 1;
10470         strcpy(moveList[0], "");
10471         strcpy(parseList[0], "");
10472         CopyBoard(boards[1], initial_position);
10473         DisplayMessage("", _("Black to play"));
10474     } else {
10475         currentMove = forwardMostMove = backwardMostMove = 0;
10476         DisplayMessage("", _("White to play"));
10477     }
10478     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10479     SendBoard(&first, forwardMostMove);
10480     if (appData.debugMode) {
10481 int i, j;
10482   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10483   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10484         fprintf(debugFP, "Load Position\n");
10485     }
10486
10487     if (positionNumber > 1) {
10488         sprintf(line, "%s %d", title, positionNumber);
10489         DisplayTitle(line);
10490     } else {
10491         DisplayTitle(title);
10492     }
10493     gameMode = EditGame;
10494     ModeHighlight();
10495     ResetClocks();
10496     timeRemaining[0][1] = whiteTimeRemaining;
10497     timeRemaining[1][1] = blackTimeRemaining;
10498     DrawPosition(FALSE, boards[currentMove]);
10499    
10500     return TRUE;
10501 }
10502
10503
10504 void
10505 CopyPlayerNameIntoFileName(dest, src)
10506      char **dest, *src;
10507 {
10508     while (*src != NULLCHAR && *src != ',') {
10509         if (*src == ' ') {
10510             *(*dest)++ = '_';
10511             src++;
10512         } else {
10513             *(*dest)++ = *src++;
10514         }
10515     }
10516 }
10517
10518 char *DefaultFileName(ext)
10519      char *ext;
10520 {
10521     static char def[MSG_SIZ];
10522     char *p;
10523
10524     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10525         p = def;
10526         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10527         *p++ = '-';
10528         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10529         *p++ = '.';
10530         strcpy(p, ext);
10531     } else {
10532         def[0] = NULLCHAR;
10533     }
10534     return def;
10535 }
10536
10537 /* Save the current game to the given file */
10538 int
10539 SaveGameToFile(filename, append)
10540      char *filename;
10541      int append;
10542 {
10543     FILE *f;
10544     char buf[MSG_SIZ];
10545
10546     if (strcmp(filename, "-") == 0) {
10547         return SaveGame(stdout, 0, NULL);
10548     } else {
10549         f = fopen(filename, append ? "a" : "w");
10550         if (f == NULL) {
10551             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10552             DisplayError(buf, errno);
10553             return FALSE;
10554         } else {
10555             return SaveGame(f, 0, NULL);
10556         }
10557     }
10558 }
10559
10560 char *
10561 SavePart(str)
10562      char *str;
10563 {
10564     static char buf[MSG_SIZ];
10565     char *p;
10566     
10567     p = strchr(str, ' ');
10568     if (p == NULL) return str;
10569     strncpy(buf, str, p - str);
10570     buf[p - str] = NULLCHAR;
10571     return buf;
10572 }
10573
10574 #define PGN_MAX_LINE 75
10575
10576 #define PGN_SIDE_WHITE  0
10577 #define PGN_SIDE_BLACK  1
10578
10579 /* [AS] */
10580 static int FindFirstMoveOutOfBook( int side )
10581 {
10582     int result = -1;
10583
10584     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10585         int index = backwardMostMove;
10586         int has_book_hit = 0;
10587
10588         if( (index % 2) != side ) {
10589             index++;
10590         }
10591
10592         while( index < forwardMostMove ) {
10593             /* Check to see if engine is in book */
10594             int depth = pvInfoList[index].depth;
10595             int score = pvInfoList[index].score;
10596             int in_book = 0;
10597
10598             if( depth <= 2 ) {
10599                 in_book = 1;
10600             }
10601             else if( score == 0 && depth == 63 ) {
10602                 in_book = 1; /* Zappa */
10603             }
10604             else if( score == 2 && depth == 99 ) {
10605                 in_book = 1; /* Abrok */
10606             }
10607
10608             has_book_hit += in_book;
10609
10610             if( ! in_book ) {
10611                 result = index;
10612
10613                 break;
10614             }
10615
10616             index += 2;
10617         }
10618     }
10619
10620     return result;
10621 }
10622
10623 /* [AS] */
10624 void GetOutOfBookInfo( char * buf )
10625 {
10626     int oob[2];
10627     int i;
10628     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10629
10630     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10631     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10632
10633     *buf = '\0';
10634
10635     if( oob[0] >= 0 || oob[1] >= 0 ) {
10636         for( i=0; i<2; i++ ) {
10637             int idx = oob[i];
10638
10639             if( idx >= 0 ) {
10640                 if( i > 0 && oob[0] >= 0 ) {
10641                     strcat( buf, "   " );
10642                 }
10643
10644                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10645                 sprintf( buf+strlen(buf), "%s%.2f", 
10646                     pvInfoList[idx].score >= 0 ? "+" : "",
10647                     pvInfoList[idx].score / 100.0 );
10648             }
10649         }
10650     }
10651 }
10652
10653 /* Save game in PGN style and close the file */
10654 int
10655 SaveGamePGN(f)
10656      FILE *f;
10657 {
10658     int i, offset, linelen, newblock;
10659     time_t tm;
10660 //    char *movetext;
10661     char numtext[32];
10662     int movelen, numlen, blank;
10663     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10664
10665     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10666     
10667     tm = time((time_t *) NULL);
10668     
10669     PrintPGNTags(f, &gameInfo);
10670     
10671     if (backwardMostMove > 0 || startedFromSetupPosition) {
10672         char *fen = PositionToFEN(backwardMostMove, NULL);
10673         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10674         fprintf(f, "\n{--------------\n");
10675         PrintPosition(f, backwardMostMove);
10676         fprintf(f, "--------------}\n");
10677         free(fen);
10678     }
10679     else {
10680         /* [AS] Out of book annotation */
10681         if( appData.saveOutOfBookInfo ) {
10682             char buf[64];
10683
10684             GetOutOfBookInfo( buf );
10685
10686             if( buf[0] != '\0' ) {
10687                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10688             }
10689         }
10690
10691         fprintf(f, "\n");
10692     }
10693
10694     i = backwardMostMove;
10695     linelen = 0;
10696     newblock = TRUE;
10697
10698     while (i < forwardMostMove) {
10699         /* Print comments preceding this move */
10700         if (commentList[i] != NULL) {
10701             if (linelen > 0) fprintf(f, "\n");
10702             fprintf(f, "%s", commentList[i]);
10703             linelen = 0;
10704             newblock = TRUE;
10705         }
10706
10707         /* Format move number */
10708         if ((i % 2) == 0) {
10709             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10710         } else {
10711             if (newblock) {
10712                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10713             } else {
10714                 numtext[0] = NULLCHAR;
10715             }
10716         }
10717         numlen = strlen(numtext);
10718         newblock = FALSE;
10719
10720         /* Print move number */
10721         blank = linelen > 0 && numlen > 0;
10722         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10723             fprintf(f, "\n");
10724             linelen = 0;
10725             blank = 0;
10726         }
10727         if (blank) {
10728             fprintf(f, " ");
10729             linelen++;
10730         }
10731         fprintf(f, "%s", numtext);
10732         linelen += numlen;
10733
10734         /* Get move */
10735         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10736         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10737
10738         /* Print move */
10739         blank = linelen > 0 && movelen > 0;
10740         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10741             fprintf(f, "\n");
10742             linelen = 0;
10743             blank = 0;
10744         }
10745         if (blank) {
10746             fprintf(f, " ");
10747             linelen++;
10748         }
10749         fprintf(f, "%s", move_buffer);
10750         linelen += movelen;
10751
10752         /* [AS] Add PV info if present */
10753         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10754             /* [HGM] add time */
10755             char buf[MSG_SIZ]; int seconds;
10756
10757             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10758
10759             if( seconds <= 0) buf[0] = 0; else
10760             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10761                 seconds = (seconds + 4)/10; // round to full seconds
10762                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10763                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10764             }
10765
10766             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10767                 pvInfoList[i].score >= 0 ? "+" : "",
10768                 pvInfoList[i].score / 100.0,
10769                 pvInfoList[i].depth,
10770                 buf );
10771
10772             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10773
10774             /* Print score/depth */
10775             blank = linelen > 0 && movelen > 0;
10776             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10777                 fprintf(f, "\n");
10778                 linelen = 0;
10779                 blank = 0;
10780             }
10781             if (blank) {
10782                 fprintf(f, " ");
10783                 linelen++;
10784             }
10785             fprintf(f, "%s", move_buffer);
10786             linelen += movelen;
10787         }
10788
10789         i++;
10790     }
10791     
10792     /* Start a new line */
10793     if (linelen > 0) fprintf(f, "\n");
10794
10795     /* Print comments after last move */
10796     if (commentList[i] != NULL) {
10797         fprintf(f, "%s\n", commentList[i]);
10798     }
10799
10800     /* Print result */
10801     if (gameInfo.resultDetails != NULL &&
10802         gameInfo.resultDetails[0] != NULLCHAR) {
10803         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10804                 PGNResult(gameInfo.result));
10805     } else {
10806         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10807     }
10808
10809     fclose(f);
10810     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10811     return TRUE;
10812 }
10813
10814 /* Save game in old style and close the file */
10815 int
10816 SaveGameOldStyle(f)
10817      FILE *f;
10818 {
10819     int i, offset;
10820     time_t tm;
10821     
10822     tm = time((time_t *) NULL);
10823     
10824     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10825     PrintOpponents(f);
10826     
10827     if (backwardMostMove > 0 || startedFromSetupPosition) {
10828         fprintf(f, "\n[--------------\n");
10829         PrintPosition(f, backwardMostMove);
10830         fprintf(f, "--------------]\n");
10831     } else {
10832         fprintf(f, "\n");
10833     }
10834
10835     i = backwardMostMove;
10836     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10837
10838     while (i < forwardMostMove) {
10839         if (commentList[i] != NULL) {
10840             fprintf(f, "[%s]\n", commentList[i]);
10841         }
10842
10843         if ((i % 2) == 1) {
10844             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10845             i++;
10846         } else {
10847             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10848             i++;
10849             if (commentList[i] != NULL) {
10850                 fprintf(f, "\n");
10851                 continue;
10852             }
10853             if (i >= forwardMostMove) {
10854                 fprintf(f, "\n");
10855                 break;
10856             }
10857             fprintf(f, "%s\n", parseList[i]);
10858             i++;
10859         }
10860     }
10861     
10862     if (commentList[i] != NULL) {
10863         fprintf(f, "[%s]\n", commentList[i]);
10864     }
10865
10866     /* This isn't really the old style, but it's close enough */
10867     if (gameInfo.resultDetails != NULL &&
10868         gameInfo.resultDetails[0] != NULLCHAR) {
10869         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10870                 gameInfo.resultDetails);
10871     } else {
10872         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10873     }
10874
10875     fclose(f);
10876     return TRUE;
10877 }
10878
10879 /* Save the current game to open file f and close the file */
10880 int
10881 SaveGame(f, dummy, dummy2)
10882      FILE *f;
10883      int dummy;
10884      char *dummy2;
10885 {
10886     if (gameMode == EditPosition) EditPositionDone(TRUE);
10887     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10888     if (appData.oldSaveStyle)
10889       return SaveGameOldStyle(f);
10890     else
10891       return SaveGamePGN(f);
10892 }
10893
10894 /* Save the current position to the given file */
10895 int
10896 SavePositionToFile(filename)
10897      char *filename;
10898 {
10899     FILE *f;
10900     char buf[MSG_SIZ];
10901
10902     if (strcmp(filename, "-") == 0) {
10903         return SavePosition(stdout, 0, NULL);
10904     } else {
10905         f = fopen(filename, "a");
10906         if (f == NULL) {
10907             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10908             DisplayError(buf, errno);
10909             return FALSE;
10910         } else {
10911             SavePosition(f, 0, NULL);
10912             return TRUE;
10913         }
10914     }
10915 }
10916
10917 /* Save the current position to the given open file and close the file */
10918 int
10919 SavePosition(f, dummy, dummy2)
10920      FILE *f;
10921      int dummy;
10922      char *dummy2;
10923 {
10924     time_t tm;
10925     char *fen;
10926     
10927     if (gameMode == EditPosition) EditPositionDone(TRUE);
10928     if (appData.oldSaveStyle) {
10929         tm = time((time_t *) NULL);
10930     
10931         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10932         PrintOpponents(f);
10933         fprintf(f, "[--------------\n");
10934         PrintPosition(f, currentMove);
10935         fprintf(f, "--------------]\n");
10936     } else {
10937         fen = PositionToFEN(currentMove, NULL);
10938         fprintf(f, "%s\n", fen);
10939         free(fen);
10940     }
10941     fclose(f);
10942     return TRUE;
10943 }
10944
10945 void
10946 ReloadCmailMsgEvent(unregister)
10947      int unregister;
10948 {
10949 #if !WIN32
10950     static char *inFilename = NULL;
10951     static char *outFilename;
10952     int i;
10953     struct stat inbuf, outbuf;
10954     int status;
10955     
10956     /* Any registered moves are unregistered if unregister is set, */
10957     /* i.e. invoked by the signal handler */
10958     if (unregister) {
10959         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10960             cmailMoveRegistered[i] = FALSE;
10961             if (cmailCommentList[i] != NULL) {
10962                 free(cmailCommentList[i]);
10963                 cmailCommentList[i] = NULL;
10964             }
10965         }
10966         nCmailMovesRegistered = 0;
10967     }
10968
10969     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10970         cmailResult[i] = CMAIL_NOT_RESULT;
10971     }
10972     nCmailResults = 0;
10973
10974     if (inFilename == NULL) {
10975         /* Because the filenames are static they only get malloced once  */
10976         /* and they never get freed                                      */
10977         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10978         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10979
10980         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10981         sprintf(outFilename, "%s.out", appData.cmailGameName);
10982     }
10983     
10984     status = stat(outFilename, &outbuf);
10985     if (status < 0) {
10986         cmailMailedMove = FALSE;
10987     } else {
10988         status = stat(inFilename, &inbuf);
10989         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10990     }
10991     
10992     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10993        counts the games, notes how each one terminated, etc.
10994        
10995        It would be nice to remove this kludge and instead gather all
10996        the information while building the game list.  (And to keep it
10997        in the game list nodes instead of having a bunch of fixed-size
10998        parallel arrays.)  Note this will require getting each game's
10999        termination from the PGN tags, as the game list builder does
11000        not process the game moves.  --mann
11001        */
11002     cmailMsgLoaded = TRUE;
11003     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11004     
11005     /* Load first game in the file or popup game menu */
11006     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11007
11008 #endif /* !WIN32 */
11009     return;
11010 }
11011
11012 int
11013 RegisterMove()
11014 {
11015     FILE *f;
11016     char string[MSG_SIZ];
11017
11018     if (   cmailMailedMove
11019         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11020         return TRUE;            /* Allow free viewing  */
11021     }
11022
11023     /* Unregister move to ensure that we don't leave RegisterMove        */
11024     /* with the move registered when the conditions for registering no   */
11025     /* longer hold                                                       */
11026     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11027         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11028         nCmailMovesRegistered --;
11029
11030         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11031           {
11032               free(cmailCommentList[lastLoadGameNumber - 1]);
11033               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11034           }
11035     }
11036
11037     if (cmailOldMove == -1) {
11038         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11039         return FALSE;
11040     }
11041
11042     if (currentMove > cmailOldMove + 1) {
11043         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11044         return FALSE;
11045     }
11046
11047     if (currentMove < cmailOldMove) {
11048         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11049         return FALSE;
11050     }
11051
11052     if (forwardMostMove > currentMove) {
11053         /* Silently truncate extra moves */
11054         TruncateGame();
11055     }
11056
11057     if (   (currentMove == cmailOldMove + 1)
11058         || (   (currentMove == cmailOldMove)
11059             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11060                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11061         if (gameInfo.result != GameUnfinished) {
11062             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11063         }
11064
11065         if (commentList[currentMove] != NULL) {
11066             cmailCommentList[lastLoadGameNumber - 1]
11067               = StrSave(commentList[currentMove]);
11068         }
11069         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11070
11071         if (appData.debugMode)
11072           fprintf(debugFP, "Saving %s for game %d\n",
11073                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11074
11075         sprintf(string,
11076                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11077         
11078         f = fopen(string, "w");
11079         if (appData.oldSaveStyle) {
11080             SaveGameOldStyle(f); /* also closes the file */
11081             
11082             sprintf(string, "%s.pos.out", appData.cmailGameName);
11083             f = fopen(string, "w");
11084             SavePosition(f, 0, NULL); /* also closes the file */
11085         } else {
11086             fprintf(f, "{--------------\n");
11087             PrintPosition(f, currentMove);
11088             fprintf(f, "--------------}\n\n");
11089             
11090             SaveGame(f, 0, NULL); /* also closes the file*/
11091         }
11092         
11093         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11094         nCmailMovesRegistered ++;
11095     } else if (nCmailGames == 1) {
11096         DisplayError(_("You have not made a move yet"), 0);
11097         return FALSE;
11098     }
11099
11100     return TRUE;
11101 }
11102
11103 void
11104 MailMoveEvent()
11105 {
11106 #if !WIN32
11107     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11108     FILE *commandOutput;
11109     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11110     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11111     int nBuffers;
11112     int i;
11113     int archived;
11114     char *arcDir;
11115
11116     if (! cmailMsgLoaded) {
11117         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11118         return;
11119     }
11120
11121     if (nCmailGames == nCmailResults) {
11122         DisplayError(_("No unfinished games"), 0);
11123         return;
11124     }
11125
11126 #if CMAIL_PROHIBIT_REMAIL
11127     if (cmailMailedMove) {
11128         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);
11129         DisplayError(msg, 0);
11130         return;
11131     }
11132 #endif
11133
11134     if (! (cmailMailedMove || RegisterMove())) return;
11135     
11136     if (   cmailMailedMove
11137         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11138         sprintf(string, partCommandString,
11139                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11140         commandOutput = popen(string, "r");
11141
11142         if (commandOutput == NULL) {
11143             DisplayError(_("Failed to invoke cmail"), 0);
11144         } else {
11145             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11146                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11147             }
11148             if (nBuffers > 1) {
11149                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11150                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11151                 nBytes = MSG_SIZ - 1;
11152             } else {
11153                 (void) memcpy(msg, buffer, nBytes);
11154             }
11155             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11156
11157             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11158                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11159
11160                 archived = TRUE;
11161                 for (i = 0; i < nCmailGames; i ++) {
11162                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11163                         archived = FALSE;
11164                     }
11165                 }
11166                 if (   archived
11167                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11168                         != NULL)) {
11169                     sprintf(buffer, "%s/%s.%s.archive",
11170                             arcDir,
11171                             appData.cmailGameName,
11172                             gameInfo.date);
11173                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11174                     cmailMsgLoaded = FALSE;
11175                 }
11176             }
11177
11178             DisplayInformation(msg);
11179             pclose(commandOutput);
11180         }
11181     } else {
11182         if ((*cmailMsg) != '\0') {
11183             DisplayInformation(cmailMsg);
11184         }
11185     }
11186
11187     return;
11188 #endif /* !WIN32 */
11189 }
11190
11191 char *
11192 CmailMsg()
11193 {
11194 #if WIN32
11195     return NULL;
11196 #else
11197     int  prependComma = 0;
11198     char number[5];
11199     char string[MSG_SIZ];       /* Space for game-list */
11200     int  i;
11201     
11202     if (!cmailMsgLoaded) return "";
11203
11204     if (cmailMailedMove) {
11205         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11206     } else {
11207         /* Create a list of games left */
11208         sprintf(string, "[");
11209         for (i = 0; i < nCmailGames; i ++) {
11210             if (! (   cmailMoveRegistered[i]
11211                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11212                 if (prependComma) {
11213                     sprintf(number, ",%d", i + 1);
11214                 } else {
11215                     sprintf(number, "%d", i + 1);
11216                     prependComma = 1;
11217                 }
11218                 
11219                 strcat(string, number);
11220             }
11221         }
11222         strcat(string, "]");
11223
11224         if (nCmailMovesRegistered + nCmailResults == 0) {
11225             switch (nCmailGames) {
11226               case 1:
11227                 sprintf(cmailMsg,
11228                         _("Still need to make move for game\n"));
11229                 break;
11230                 
11231               case 2:
11232                 sprintf(cmailMsg,
11233                         _("Still need to make moves for both games\n"));
11234                 break;
11235                 
11236               default:
11237                 sprintf(cmailMsg,
11238                         _("Still need to make moves for all %d games\n"),
11239                         nCmailGames);
11240                 break;
11241             }
11242         } else {
11243             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11244               case 1:
11245                 sprintf(cmailMsg,
11246                         _("Still need to make a move for game %s\n"),
11247                         string);
11248                 break;
11249                 
11250               case 0:
11251                 if (nCmailResults == nCmailGames) {
11252                     sprintf(cmailMsg, _("No unfinished games\n"));
11253                 } else {
11254                     sprintf(cmailMsg, _("Ready to send mail\n"));
11255                 }
11256                 break;
11257                 
11258               default:
11259                 sprintf(cmailMsg,
11260                         _("Still need to make moves for games %s\n"),
11261                         string);
11262             }
11263         }
11264     }
11265     return cmailMsg;
11266 #endif /* WIN32 */
11267 }
11268
11269 void
11270 ResetGameEvent()
11271 {
11272     if (gameMode == Training)
11273       SetTrainingModeOff();
11274
11275     Reset(TRUE, TRUE);
11276     cmailMsgLoaded = FALSE;
11277     if (appData.icsActive) {
11278       SendToICS(ics_prefix);
11279       SendToICS("refresh\n");
11280     }
11281 }
11282
11283 void
11284 ExitEvent(status)
11285      int status;
11286 {
11287     exiting++;
11288     if (exiting > 2) {
11289       /* Give up on clean exit */
11290       exit(status);
11291     }
11292     if (exiting > 1) {
11293       /* Keep trying for clean exit */
11294       return;
11295     }
11296
11297     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11298
11299     if (telnetISR != NULL) {
11300       RemoveInputSource(telnetISR);
11301     }
11302     if (icsPR != NoProc) {
11303       DestroyChildProcess(icsPR, TRUE);
11304     }
11305
11306     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11307     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11308
11309     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11310     /* make sure this other one finishes before killing it!                  */
11311     if(endingGame) { int count = 0;
11312         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11313         while(endingGame && count++ < 10) DoSleep(1);
11314         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11315     }
11316
11317     /* Kill off chess programs */
11318     if (first.pr != NoProc) {
11319         ExitAnalyzeMode();
11320         
11321         DoSleep( appData.delayBeforeQuit );
11322         SendToProgram("quit\n", &first);
11323         DoSleep( appData.delayAfterQuit );
11324         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11325     }
11326     if (second.pr != NoProc) {
11327         DoSleep( appData.delayBeforeQuit );
11328         SendToProgram("quit\n", &second);
11329         DoSleep( appData.delayAfterQuit );
11330         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11331     }
11332     if (first.isr != NULL) {
11333         RemoveInputSource(first.isr);
11334     }
11335     if (second.isr != NULL) {
11336         RemoveInputSource(second.isr);
11337     }
11338
11339     ShutDownFrontEnd();
11340     exit(status);
11341 }
11342
11343 void
11344 PauseEvent()
11345 {
11346     if (appData.debugMode)
11347         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11348     if (pausing) {
11349         pausing = FALSE;
11350         ModeHighlight();
11351         if (gameMode == MachinePlaysWhite ||
11352             gameMode == MachinePlaysBlack) {
11353             StartClocks();
11354         } else {
11355             DisplayBothClocks();
11356         }
11357         if (gameMode == PlayFromGameFile) {
11358             if (appData.timeDelay >= 0) 
11359                 AutoPlayGameLoop();
11360         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11361             Reset(FALSE, TRUE);
11362             SendToICS(ics_prefix);
11363             SendToICS("refresh\n");
11364         } else if (currentMove < forwardMostMove) {
11365             ForwardInner(forwardMostMove);
11366         }
11367         pauseExamInvalid = FALSE;
11368     } else {
11369         switch (gameMode) {
11370           default:
11371             return;
11372           case IcsExamining:
11373             pauseExamForwardMostMove = forwardMostMove;
11374             pauseExamInvalid = FALSE;
11375             /* fall through */
11376           case IcsObserving:
11377           case IcsPlayingWhite:
11378           case IcsPlayingBlack:
11379             pausing = TRUE;
11380             ModeHighlight();
11381             return;
11382           case PlayFromGameFile:
11383             (void) StopLoadGameTimer();
11384             pausing = TRUE;
11385             ModeHighlight();
11386             break;
11387           case BeginningOfGame:
11388             if (appData.icsActive) return;
11389             /* else fall through */
11390           case MachinePlaysWhite:
11391           case MachinePlaysBlack:
11392           case TwoMachinesPlay:
11393             if (forwardMostMove == 0)
11394               return;           /* don't pause if no one has moved */
11395             if ((gameMode == MachinePlaysWhite &&
11396                  !WhiteOnMove(forwardMostMove)) ||
11397                 (gameMode == MachinePlaysBlack &&
11398                  WhiteOnMove(forwardMostMove))) {
11399                 StopClocks();
11400             }
11401             pausing = TRUE;
11402             ModeHighlight();
11403             break;
11404         }
11405     }
11406 }
11407
11408 void
11409 EditCommentEvent()
11410 {
11411     char title[MSG_SIZ];
11412
11413     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11414         strcpy(title, _("Edit comment"));
11415     } else {
11416         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11417                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11418                 parseList[currentMove - 1]);
11419     }
11420
11421     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11422 }
11423
11424
11425 void
11426 EditTagsEvent()
11427 {
11428     char *tags = PGNTags(&gameInfo);
11429     EditTagsPopUp(tags);
11430     free(tags);
11431 }
11432
11433 void
11434 AnalyzeModeEvent()
11435 {
11436     if (appData.noChessProgram || gameMode == AnalyzeMode)
11437       return;
11438
11439     if (gameMode != AnalyzeFile) {
11440         if (!appData.icsEngineAnalyze) {
11441                EditGameEvent();
11442                if (gameMode != EditGame) return;
11443         }
11444         ResurrectChessProgram();
11445         SendToProgram("analyze\n", &first);
11446         first.analyzing = TRUE;
11447         /*first.maybeThinking = TRUE;*/
11448         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11449         EngineOutputPopUp();
11450     }
11451     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11452     pausing = FALSE;
11453     ModeHighlight();
11454     SetGameInfo();
11455
11456     StartAnalysisClock();
11457     GetTimeMark(&lastNodeCountTime);
11458     lastNodeCount = 0;
11459 }
11460
11461 void
11462 AnalyzeFileEvent()
11463 {
11464     if (appData.noChessProgram || gameMode == AnalyzeFile)
11465       return;
11466
11467     if (gameMode != AnalyzeMode) {
11468         EditGameEvent();
11469         if (gameMode != EditGame) return;
11470         ResurrectChessProgram();
11471         SendToProgram("analyze\n", &first);
11472         first.analyzing = TRUE;
11473         /*first.maybeThinking = TRUE;*/
11474         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11475         EngineOutputPopUp();
11476     }
11477     gameMode = AnalyzeFile;
11478     pausing = FALSE;
11479     ModeHighlight();
11480     SetGameInfo();
11481
11482     StartAnalysisClock();
11483     GetTimeMark(&lastNodeCountTime);
11484     lastNodeCount = 0;
11485 }
11486
11487 void
11488 MachineWhiteEvent()
11489 {
11490     char buf[MSG_SIZ];
11491     char *bookHit = NULL;
11492
11493     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11494       return;
11495
11496
11497     if (gameMode == PlayFromGameFile || 
11498         gameMode == TwoMachinesPlay  || 
11499         gameMode == Training         || 
11500         gameMode == AnalyzeMode      || 
11501         gameMode == EndOfGame)
11502         EditGameEvent();
11503
11504     if (gameMode == EditPosition) 
11505         EditPositionDone(TRUE);
11506
11507     if (!WhiteOnMove(currentMove)) {
11508         DisplayError(_("It is not White's turn"), 0);
11509         return;
11510     }
11511   
11512     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11513       ExitAnalyzeMode();
11514
11515     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11516         gameMode == AnalyzeFile)
11517         TruncateGame();
11518
11519     ResurrectChessProgram();    /* in case it isn't running */
11520     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11521         gameMode = MachinePlaysWhite;
11522         ResetClocks();
11523     } else
11524     gameMode = MachinePlaysWhite;
11525     pausing = FALSE;
11526     ModeHighlight();
11527     SetGameInfo();
11528     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11529     DisplayTitle(buf);
11530     if (first.sendName) {
11531       sprintf(buf, "name %s\n", gameInfo.black);
11532       SendToProgram(buf, &first);
11533     }
11534     if (first.sendTime) {
11535       if (first.useColors) {
11536         SendToProgram("black\n", &first); /*gnu kludge*/
11537       }
11538       SendTimeRemaining(&first, TRUE);
11539     }
11540     if (first.useColors) {
11541       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11542     }
11543     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11544     SetMachineThinkingEnables();
11545     first.maybeThinking = TRUE;
11546     StartClocks();
11547     firstMove = FALSE;
11548
11549     if (appData.autoFlipView && !flipView) {
11550       flipView = !flipView;
11551       DrawPosition(FALSE, NULL);
11552       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11553     }
11554
11555     if(bookHit) { // [HGM] book: simulate book reply
11556         static char bookMove[MSG_SIZ]; // a bit generous?
11557
11558         programStats.nodes = programStats.depth = programStats.time = 
11559         programStats.score = programStats.got_only_move = 0;
11560         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11561
11562         strcpy(bookMove, "move ");
11563         strcat(bookMove, bookHit);
11564         HandleMachineMove(bookMove, &first);
11565     }
11566 }
11567
11568 void
11569 MachineBlackEvent()
11570 {
11571     char buf[MSG_SIZ];
11572    char *bookHit = NULL;
11573
11574     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11575         return;
11576
11577
11578     if (gameMode == PlayFromGameFile || 
11579         gameMode == TwoMachinesPlay  || 
11580         gameMode == Training         || 
11581         gameMode == AnalyzeMode      || 
11582         gameMode == EndOfGame)
11583         EditGameEvent();
11584
11585     if (gameMode == EditPosition) 
11586         EditPositionDone(TRUE);
11587
11588     if (WhiteOnMove(currentMove)) {
11589         DisplayError(_("It is not Black's turn"), 0);
11590         return;
11591     }
11592     
11593     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11594       ExitAnalyzeMode();
11595
11596     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11597         gameMode == AnalyzeFile)
11598         TruncateGame();
11599
11600     ResurrectChessProgram();    /* in case it isn't running */
11601     gameMode = MachinePlaysBlack;
11602     pausing = FALSE;
11603     ModeHighlight();
11604     SetGameInfo();
11605     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11606     DisplayTitle(buf);
11607     if (first.sendName) {
11608       sprintf(buf, "name %s\n", gameInfo.white);
11609       SendToProgram(buf, &first);
11610     }
11611     if (first.sendTime) {
11612       if (first.useColors) {
11613         SendToProgram("white\n", &first); /*gnu kludge*/
11614       }
11615       SendTimeRemaining(&first, FALSE);
11616     }
11617     if (first.useColors) {
11618       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11619     }
11620     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11621     SetMachineThinkingEnables();
11622     first.maybeThinking = TRUE;
11623     StartClocks();
11624
11625     if (appData.autoFlipView && flipView) {
11626       flipView = !flipView;
11627       DrawPosition(FALSE, NULL);
11628       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11629     }
11630     if(bookHit) { // [HGM] book: simulate book reply
11631         static char bookMove[MSG_SIZ]; // a bit generous?
11632
11633         programStats.nodes = programStats.depth = programStats.time = 
11634         programStats.score = programStats.got_only_move = 0;
11635         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11636
11637         strcpy(bookMove, "move ");
11638         strcat(bookMove, bookHit);
11639         HandleMachineMove(bookMove, &first);
11640     }
11641 }
11642
11643
11644 void
11645 DisplayTwoMachinesTitle()
11646 {
11647     char buf[MSG_SIZ];
11648     if (appData.matchGames > 0) {
11649         if (first.twoMachinesColor[0] == 'w') {
11650             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11651                     gameInfo.white, gameInfo.black,
11652                     first.matchWins, second.matchWins,
11653                     matchGame - 1 - (first.matchWins + second.matchWins));
11654         } else {
11655             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11656                     gameInfo.white, gameInfo.black,
11657                     second.matchWins, first.matchWins,
11658                     matchGame - 1 - (first.matchWins + second.matchWins));
11659         }
11660     } else {
11661         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11662     }
11663     DisplayTitle(buf);
11664 }
11665
11666 void
11667 TwoMachinesEvent P((void))
11668 {
11669     int i;
11670     char buf[MSG_SIZ];
11671     ChessProgramState *onmove;
11672     char *bookHit = NULL;
11673     
11674     if (appData.noChessProgram) return;
11675
11676     switch (gameMode) {
11677       case TwoMachinesPlay:
11678         return;
11679       case MachinePlaysWhite:
11680       case MachinePlaysBlack:
11681         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11682             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11683             return;
11684         }
11685         /* fall through */
11686       case BeginningOfGame:
11687       case PlayFromGameFile:
11688       case EndOfGame:
11689         EditGameEvent();
11690         if (gameMode != EditGame) return;
11691         break;
11692       case EditPosition:
11693         EditPositionDone(TRUE);
11694         break;
11695       case AnalyzeMode:
11696       case AnalyzeFile:
11697         ExitAnalyzeMode();
11698         break;
11699       case EditGame:
11700       default:
11701         break;
11702     }
11703
11704 //    forwardMostMove = currentMove;
11705     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11706     ResurrectChessProgram();    /* in case first program isn't running */
11707
11708     if (second.pr == NULL) {
11709         StartChessProgram(&second);
11710         if (second.protocolVersion == 1) {
11711           TwoMachinesEventIfReady();
11712         } else {
11713           /* kludge: allow timeout for initial "feature" command */
11714           FreezeUI();
11715           DisplayMessage("", _("Starting second chess program"));
11716           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11717         }
11718         return;
11719     }
11720     DisplayMessage("", "");
11721     InitChessProgram(&second, FALSE);
11722     SendToProgram("force\n", &second);
11723     if (startedFromSetupPosition) {
11724         SendBoard(&second, backwardMostMove);
11725     if (appData.debugMode) {
11726         fprintf(debugFP, "Two Machines\n");
11727     }
11728     }
11729     for (i = backwardMostMove; i < forwardMostMove; i++) {
11730         SendMoveToProgram(i, &second);
11731     }
11732
11733     gameMode = TwoMachinesPlay;
11734     pausing = FALSE;
11735     ModeHighlight();
11736     SetGameInfo();
11737     DisplayTwoMachinesTitle();
11738     firstMove = TRUE;
11739     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11740         onmove = &first;
11741     } else {
11742         onmove = &second;
11743     }
11744
11745     SendToProgram(first.computerString, &first);
11746     if (first.sendName) {
11747       sprintf(buf, "name %s\n", second.tidy);
11748       SendToProgram(buf, &first);
11749     }
11750     SendToProgram(second.computerString, &second);
11751     if (second.sendName) {
11752       sprintf(buf, "name %s\n", first.tidy);
11753       SendToProgram(buf, &second);
11754     }
11755
11756     ResetClocks();
11757     if (!first.sendTime || !second.sendTime) {
11758         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11759         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11760     }
11761     if (onmove->sendTime) {
11762       if (onmove->useColors) {
11763         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11764       }
11765       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11766     }
11767     if (onmove->useColors) {
11768       SendToProgram(onmove->twoMachinesColor, onmove);
11769     }
11770     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11771 //    SendToProgram("go\n", onmove);
11772     onmove->maybeThinking = TRUE;
11773     SetMachineThinkingEnables();
11774
11775     StartClocks();
11776
11777     if(bookHit) { // [HGM] book: simulate book reply
11778         static char bookMove[MSG_SIZ]; // a bit generous?
11779
11780         programStats.nodes = programStats.depth = programStats.time = 
11781         programStats.score = programStats.got_only_move = 0;
11782         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11783
11784         strcpy(bookMove, "move ");
11785         strcat(bookMove, bookHit);
11786         savedMessage = bookMove; // args for deferred call
11787         savedState = onmove;
11788         ScheduleDelayedEvent(DeferredBookMove, 1);
11789     }
11790 }
11791
11792 void
11793 TrainingEvent()
11794 {
11795     if (gameMode == Training) {
11796       SetTrainingModeOff();
11797       gameMode = PlayFromGameFile;
11798       DisplayMessage("", _("Training mode off"));
11799     } else {
11800       gameMode = Training;
11801       animateTraining = appData.animate;
11802
11803       /* make sure we are not already at the end of the game */
11804       if (currentMove < forwardMostMove) {
11805         SetTrainingModeOn();
11806         DisplayMessage("", _("Training mode on"));
11807       } else {
11808         gameMode = PlayFromGameFile;
11809         DisplayError(_("Already at end of game"), 0);
11810       }
11811     }
11812     ModeHighlight();
11813 }
11814
11815 void
11816 IcsClientEvent()
11817 {
11818     if (!appData.icsActive) return;
11819     switch (gameMode) {
11820       case IcsPlayingWhite:
11821       case IcsPlayingBlack:
11822       case IcsObserving:
11823       case IcsIdle:
11824       case BeginningOfGame:
11825       case IcsExamining:
11826         return;
11827
11828       case EditGame:
11829         break;
11830
11831       case EditPosition:
11832         EditPositionDone(TRUE);
11833         break;
11834
11835       case AnalyzeMode:
11836       case AnalyzeFile:
11837         ExitAnalyzeMode();
11838         break;
11839         
11840       default:
11841         EditGameEvent();
11842         break;
11843     }
11844
11845     gameMode = IcsIdle;
11846     ModeHighlight();
11847     return;
11848 }
11849
11850
11851 void
11852 EditGameEvent()
11853 {
11854     int i;
11855
11856     switch (gameMode) {
11857       case Training:
11858         SetTrainingModeOff();
11859         break;
11860       case MachinePlaysWhite:
11861       case MachinePlaysBlack:
11862       case BeginningOfGame:
11863         SendToProgram("force\n", &first);
11864         SetUserThinkingEnables();
11865         break;
11866       case PlayFromGameFile:
11867         (void) StopLoadGameTimer();
11868         if (gameFileFP != NULL) {
11869             gameFileFP = NULL;
11870         }
11871         break;
11872       case EditPosition:
11873         EditPositionDone(TRUE);
11874         break;
11875       case AnalyzeMode:
11876       case AnalyzeFile:
11877         ExitAnalyzeMode();
11878         SendToProgram("force\n", &first);
11879         break;
11880       case TwoMachinesPlay:
11881         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11882         ResurrectChessProgram();
11883         SetUserThinkingEnables();
11884         break;
11885       case EndOfGame:
11886         ResurrectChessProgram();
11887         break;
11888       case IcsPlayingBlack:
11889       case IcsPlayingWhite:
11890         DisplayError(_("Warning: You are still playing a game"), 0);
11891         break;
11892       case IcsObserving:
11893         DisplayError(_("Warning: You are still observing a game"), 0);
11894         break;
11895       case IcsExamining:
11896         DisplayError(_("Warning: You are still examining a game"), 0);
11897         break;
11898       case IcsIdle:
11899         break;
11900       case EditGame:
11901       default:
11902         return;
11903     }
11904     
11905     pausing = FALSE;
11906     StopClocks();
11907     first.offeredDraw = second.offeredDraw = 0;
11908
11909     if (gameMode == PlayFromGameFile) {
11910         whiteTimeRemaining = timeRemaining[0][currentMove];
11911         blackTimeRemaining = timeRemaining[1][currentMove];
11912         DisplayTitle("");
11913     }
11914
11915     if (gameMode == MachinePlaysWhite ||
11916         gameMode == MachinePlaysBlack ||
11917         gameMode == TwoMachinesPlay ||
11918         gameMode == EndOfGame) {
11919         i = forwardMostMove;
11920         while (i > currentMove) {
11921             SendToProgram("undo\n", &first);
11922             i--;
11923         }
11924         whiteTimeRemaining = timeRemaining[0][currentMove];
11925         blackTimeRemaining = timeRemaining[1][currentMove];
11926         DisplayBothClocks();
11927         if (whiteFlag || blackFlag) {
11928             whiteFlag = blackFlag = 0;
11929         }
11930         DisplayTitle("");
11931     }           
11932     
11933     gameMode = EditGame;
11934     ModeHighlight();
11935     SetGameInfo();
11936 }
11937
11938
11939 void
11940 EditPositionEvent()
11941 {
11942     if (gameMode == EditPosition) {
11943         EditGameEvent();
11944         return;
11945     }
11946     
11947     EditGameEvent();
11948     if (gameMode != EditGame) return;
11949     
11950     gameMode = EditPosition;
11951     ModeHighlight();
11952     SetGameInfo();
11953     if (currentMove > 0)
11954       CopyBoard(boards[0], boards[currentMove]);
11955     
11956     blackPlaysFirst = !WhiteOnMove(currentMove);
11957     ResetClocks();
11958     currentMove = forwardMostMove = backwardMostMove = 0;
11959     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11960     DisplayMove(-1);
11961 }
11962
11963 void
11964 ExitAnalyzeMode()
11965 {
11966     /* [DM] icsEngineAnalyze - possible call from other functions */
11967     if (appData.icsEngineAnalyze) {
11968         appData.icsEngineAnalyze = FALSE;
11969
11970         DisplayMessage("",_("Close ICS engine analyze..."));
11971     }
11972     if (first.analysisSupport && first.analyzing) {
11973       SendToProgram("exit\n", &first);
11974       first.analyzing = FALSE;
11975     }
11976     thinkOutput[0] = NULLCHAR;
11977 }
11978
11979 void
11980 EditPositionDone(Boolean fakeRights)
11981 {
11982     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11983
11984     startedFromSetupPosition = TRUE;
11985     InitChessProgram(&first, FALSE);
11986     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11987       boards[0][EP_STATUS] = EP_NONE;
11988       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11989     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11990         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11991         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11992       } else boards[0][CASTLING][2] = NoRights;
11993     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11994         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11995         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11996       } else boards[0][CASTLING][5] = NoRights;
11997     }
11998     SendToProgram("force\n", &first);
11999     if (blackPlaysFirst) {
12000         strcpy(moveList[0], "");
12001         strcpy(parseList[0], "");
12002         currentMove = forwardMostMove = backwardMostMove = 1;
12003         CopyBoard(boards[1], boards[0]);
12004     } else {
12005         currentMove = forwardMostMove = backwardMostMove = 0;
12006     }
12007     SendBoard(&first, forwardMostMove);
12008     if (appData.debugMode) {
12009         fprintf(debugFP, "EditPosDone\n");
12010     }
12011     DisplayTitle("");
12012     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12013     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12014     gameMode = EditGame;
12015     ModeHighlight();
12016     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12017     ClearHighlights(); /* [AS] */
12018 }
12019
12020 /* Pause for `ms' milliseconds */
12021 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12022 void
12023 TimeDelay(ms)
12024      long ms;
12025 {
12026     TimeMark m1, m2;
12027
12028     GetTimeMark(&m1);
12029     do {
12030         GetTimeMark(&m2);
12031     } while (SubtractTimeMarks(&m2, &m1) < ms);
12032 }
12033
12034 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12035 void
12036 SendMultiLineToICS(buf)
12037      char *buf;
12038 {
12039     char temp[MSG_SIZ+1], *p;
12040     int len;
12041
12042     len = strlen(buf);
12043     if (len > MSG_SIZ)
12044       len = MSG_SIZ;
12045   
12046     strncpy(temp, buf, len);
12047     temp[len] = 0;
12048
12049     p = temp;
12050     while (*p) {
12051         if (*p == '\n' || *p == '\r')
12052           *p = ' ';
12053         ++p;
12054     }
12055
12056     strcat(temp, "\n");
12057     SendToICS(temp);
12058     SendToPlayer(temp, strlen(temp));
12059 }
12060
12061 void
12062 SetWhiteToPlayEvent()
12063 {
12064     if (gameMode == EditPosition) {
12065         blackPlaysFirst = FALSE;
12066         DisplayBothClocks();    /* works because currentMove is 0 */
12067     } else if (gameMode == IcsExamining) {
12068         SendToICS(ics_prefix);
12069         SendToICS("tomove white\n");
12070     }
12071 }
12072
12073 void
12074 SetBlackToPlayEvent()
12075 {
12076     if (gameMode == EditPosition) {
12077         blackPlaysFirst = TRUE;
12078         currentMove = 1;        /* kludge */
12079         DisplayBothClocks();
12080         currentMove = 0;
12081     } else if (gameMode == IcsExamining) {
12082         SendToICS(ics_prefix);
12083         SendToICS("tomove black\n");
12084     }
12085 }
12086
12087 void
12088 EditPositionMenuEvent(selection, x, y)
12089      ChessSquare selection;
12090      int x, y;
12091 {
12092     char buf[MSG_SIZ];
12093     ChessSquare piece = boards[0][y][x];
12094
12095     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12096
12097     switch (selection) {
12098       case ClearBoard:
12099         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12100             SendToICS(ics_prefix);
12101             SendToICS("bsetup clear\n");
12102         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12103             SendToICS(ics_prefix);
12104             SendToICS("clearboard\n");
12105         } else {
12106             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12107                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12108                 for (y = 0; y < BOARD_HEIGHT; y++) {
12109                     if (gameMode == IcsExamining) {
12110                         if (boards[currentMove][y][x] != EmptySquare) {
12111                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12112                                     AAA + x, ONE + y);
12113                             SendToICS(buf);
12114                         }
12115                     } else {
12116                         boards[0][y][x] = p;
12117                     }
12118                 }
12119             }
12120         }
12121         if (gameMode == EditPosition) {
12122             DrawPosition(FALSE, boards[0]);
12123         }
12124         break;
12125
12126       case WhitePlay:
12127         SetWhiteToPlayEvent();
12128         break;
12129
12130       case BlackPlay:
12131         SetBlackToPlayEvent();
12132         break;
12133
12134       case EmptySquare:
12135         if (gameMode == IcsExamining) {
12136             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12137             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12138             SendToICS(buf);
12139         } else {
12140             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12141                 if(x == BOARD_LEFT-2) {
12142                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12143                     boards[0][y][1] = 0;
12144                 } else
12145                 if(x == BOARD_RGHT+1) {
12146                     if(y >= gameInfo.holdingsSize) break;
12147                     boards[0][y][BOARD_WIDTH-2] = 0;
12148                 } else break;
12149             }
12150             boards[0][y][x] = EmptySquare;
12151             DrawPosition(FALSE, boards[0]);
12152         }
12153         break;
12154
12155       case PromotePiece:
12156         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12157            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12158             selection = (ChessSquare) (PROMOTED piece);
12159         } else if(piece == EmptySquare) selection = WhiteSilver;
12160         else selection = (ChessSquare)((int)piece - 1);
12161         goto defaultlabel;
12162
12163       case DemotePiece:
12164         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12165            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12166             selection = (ChessSquare) (DEMOTED piece);
12167         } else if(piece == EmptySquare) selection = BlackSilver;
12168         else selection = (ChessSquare)((int)piece + 1);       
12169         goto defaultlabel;
12170
12171       case WhiteQueen:
12172       case BlackQueen:
12173         if(gameInfo.variant == VariantShatranj ||
12174            gameInfo.variant == VariantXiangqi  ||
12175            gameInfo.variant == VariantCourier  ||
12176            gameInfo.variant == VariantMakruk     )
12177             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12178         goto defaultlabel;
12179
12180       case WhiteKing:
12181       case BlackKing:
12182         if(gameInfo.variant == VariantXiangqi)
12183             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12184         if(gameInfo.variant == VariantKnightmate)
12185             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12186       default:
12187         defaultlabel:
12188         if (gameMode == IcsExamining) {
12189             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12190             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12191                     PieceToChar(selection), AAA + x, ONE + y);
12192             SendToICS(buf);
12193         } else {
12194             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12195                 int n;
12196                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12197                     n = PieceToNumber(selection - BlackPawn);
12198                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12199                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12200                     boards[0][BOARD_HEIGHT-1-n][1]++;
12201                 } else
12202                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12203                     n = PieceToNumber(selection);
12204                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12205                     boards[0][n][BOARD_WIDTH-1] = selection;
12206                     boards[0][n][BOARD_WIDTH-2]++;
12207                 }
12208             } else
12209             boards[0][y][x] = selection;
12210             DrawPosition(TRUE, boards[0]);
12211         }
12212         break;
12213     }
12214 }
12215
12216
12217 void
12218 DropMenuEvent(selection, x, y)
12219      ChessSquare selection;
12220      int x, y;
12221 {
12222     ChessMove moveType;
12223
12224     switch (gameMode) {
12225       case IcsPlayingWhite:
12226       case MachinePlaysBlack:
12227         if (!WhiteOnMove(currentMove)) {
12228             DisplayMoveError(_("It is Black's turn"));
12229             return;
12230         }
12231         moveType = WhiteDrop;
12232         break;
12233       case IcsPlayingBlack:
12234       case MachinePlaysWhite:
12235         if (WhiteOnMove(currentMove)) {
12236             DisplayMoveError(_("It is White's turn"));
12237             return;
12238         }
12239         moveType = BlackDrop;
12240         break;
12241       case EditGame:
12242         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12243         break;
12244       default:
12245         return;
12246     }
12247
12248     if (moveType == BlackDrop && selection < BlackPawn) {
12249       selection = (ChessSquare) ((int) selection
12250                                  + (int) BlackPawn - (int) WhitePawn);
12251     }
12252     if (boards[currentMove][y][x] != EmptySquare) {
12253         DisplayMoveError(_("That square is occupied"));
12254         return;
12255     }
12256
12257     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12258 }
12259
12260 void
12261 AcceptEvent()
12262 {
12263     /* Accept a pending offer of any kind from opponent */
12264     
12265     if (appData.icsActive) {
12266         SendToICS(ics_prefix);
12267         SendToICS("accept\n");
12268     } else if (cmailMsgLoaded) {
12269         if (currentMove == cmailOldMove &&
12270             commentList[cmailOldMove] != NULL &&
12271             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12272                    "Black offers a draw" : "White offers a draw")) {
12273             TruncateGame();
12274             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12275             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12276         } else {
12277             DisplayError(_("There is no pending offer on this move"), 0);
12278             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12279         }
12280     } else {
12281         /* Not used for offers from chess program */
12282     }
12283 }
12284
12285 void
12286 DeclineEvent()
12287 {
12288     /* Decline a pending offer of any kind from opponent */
12289     
12290     if (appData.icsActive) {
12291         SendToICS(ics_prefix);
12292         SendToICS("decline\n");
12293     } else if (cmailMsgLoaded) {
12294         if (currentMove == cmailOldMove &&
12295             commentList[cmailOldMove] != NULL &&
12296             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12297                    "Black offers a draw" : "White offers a draw")) {
12298 #ifdef NOTDEF
12299             AppendComment(cmailOldMove, "Draw declined", TRUE);
12300             DisplayComment(cmailOldMove - 1, "Draw declined");
12301 #endif /*NOTDEF*/
12302         } else {
12303             DisplayError(_("There is no pending offer on this move"), 0);
12304         }
12305     } else {
12306         /* Not used for offers from chess program */
12307     }
12308 }
12309
12310 void
12311 RematchEvent()
12312 {
12313     /* Issue ICS rematch command */
12314     if (appData.icsActive) {
12315         SendToICS(ics_prefix);
12316         SendToICS("rematch\n");
12317     }
12318 }
12319
12320 void
12321 CallFlagEvent()
12322 {
12323     /* Call your opponent's flag (claim a win on time) */
12324     if (appData.icsActive) {
12325         SendToICS(ics_prefix);
12326         SendToICS("flag\n");
12327     } else {
12328         switch (gameMode) {
12329           default:
12330             return;
12331           case MachinePlaysWhite:
12332             if (whiteFlag) {
12333                 if (blackFlag)
12334                   GameEnds(GameIsDrawn, "Both players ran out of time",
12335                            GE_PLAYER);
12336                 else
12337                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12338             } else {
12339                 DisplayError(_("Your opponent is not out of time"), 0);
12340             }
12341             break;
12342           case MachinePlaysBlack:
12343             if (blackFlag) {
12344                 if (whiteFlag)
12345                   GameEnds(GameIsDrawn, "Both players ran out of time",
12346                            GE_PLAYER);
12347                 else
12348                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12349             } else {
12350                 DisplayError(_("Your opponent is not out of time"), 0);
12351             }
12352             break;
12353         }
12354     }
12355 }
12356
12357 void
12358 DrawEvent()
12359 {
12360     /* Offer draw or accept pending draw offer from opponent */
12361     
12362     if (appData.icsActive) {
12363         /* Note: tournament rules require draw offers to be
12364            made after you make your move but before you punch
12365            your clock.  Currently ICS doesn't let you do that;
12366            instead, you immediately punch your clock after making
12367            a move, but you can offer a draw at any time. */
12368         
12369         SendToICS(ics_prefix);
12370         SendToICS("draw\n");
12371         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12372     } else if (cmailMsgLoaded) {
12373         if (currentMove == cmailOldMove &&
12374             commentList[cmailOldMove] != NULL &&
12375             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12376                    "Black offers a draw" : "White offers a draw")) {
12377             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12379         } else if (currentMove == cmailOldMove + 1) {
12380             char *offer = WhiteOnMove(cmailOldMove) ?
12381               "White offers a draw" : "Black offers a draw";
12382             AppendComment(currentMove, offer, TRUE);
12383             DisplayComment(currentMove - 1, offer);
12384             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12385         } else {
12386             DisplayError(_("You must make your move before offering a draw"), 0);
12387             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12388         }
12389     } else if (first.offeredDraw) {
12390         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12391     } else {
12392         if (first.sendDrawOffers) {
12393             SendToProgram("draw\n", &first);
12394             userOfferedDraw = TRUE;
12395         }
12396     }
12397 }
12398
12399 void
12400 AdjournEvent()
12401 {
12402     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12403     
12404     if (appData.icsActive) {
12405         SendToICS(ics_prefix);
12406         SendToICS("adjourn\n");
12407     } else {
12408         /* Currently GNU Chess doesn't offer or accept Adjourns */
12409     }
12410 }
12411
12412
12413 void
12414 AbortEvent()
12415 {
12416     /* Offer Abort or accept pending Abort offer from opponent */
12417     
12418     if (appData.icsActive) {
12419         SendToICS(ics_prefix);
12420         SendToICS("abort\n");
12421     } else {
12422         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12423     }
12424 }
12425
12426 void
12427 ResignEvent()
12428 {
12429     /* Resign.  You can do this even if it's not your turn. */
12430     
12431     if (appData.icsActive) {
12432         SendToICS(ics_prefix);
12433         SendToICS("resign\n");
12434     } else {
12435         switch (gameMode) {
12436           case MachinePlaysWhite:
12437             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12438             break;
12439           case MachinePlaysBlack:
12440             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12441             break;
12442           case EditGame:
12443             if (cmailMsgLoaded) {
12444                 TruncateGame();
12445                 if (WhiteOnMove(cmailOldMove)) {
12446                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12447                 } else {
12448                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12449                 }
12450                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12451             }
12452             break;
12453           default:
12454             break;
12455         }
12456     }
12457 }
12458
12459
12460 void
12461 StopObservingEvent()
12462 {
12463     /* Stop observing current games */
12464     SendToICS(ics_prefix);
12465     SendToICS("unobserve\n");
12466 }
12467
12468 void
12469 StopExaminingEvent()
12470 {
12471     /* Stop observing current game */
12472     SendToICS(ics_prefix);
12473     SendToICS("unexamine\n");
12474 }
12475
12476 void
12477 ForwardInner(target)
12478      int target;
12479 {
12480     int limit;
12481
12482     if (appData.debugMode)
12483         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12484                 target, currentMove, forwardMostMove);
12485
12486     if (gameMode == EditPosition)
12487       return;
12488
12489     if (gameMode == PlayFromGameFile && !pausing)
12490       PauseEvent();
12491     
12492     if (gameMode == IcsExamining && pausing)
12493       limit = pauseExamForwardMostMove;
12494     else
12495       limit = forwardMostMove;
12496     
12497     if (target > limit) target = limit;
12498
12499     if (target > 0 && moveList[target - 1][0]) {
12500         int fromX, fromY, toX, toY;
12501         toX = moveList[target - 1][2] - AAA;
12502         toY = moveList[target - 1][3] - ONE;
12503         if (moveList[target - 1][1] == '@') {
12504             if (appData.highlightLastMove) {
12505                 SetHighlights(-1, -1, toX, toY);
12506             }
12507         } else {
12508             fromX = moveList[target - 1][0] - AAA;
12509             fromY = moveList[target - 1][1] - ONE;
12510             if (target == currentMove + 1) {
12511                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12512             }
12513             if (appData.highlightLastMove) {
12514                 SetHighlights(fromX, fromY, toX, toY);
12515             }
12516         }
12517     }
12518     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12519         gameMode == Training || gameMode == PlayFromGameFile || 
12520         gameMode == AnalyzeFile) {
12521         while (currentMove < target) {
12522             SendMoveToProgram(currentMove++, &first);
12523         }
12524     } else {
12525         currentMove = target;
12526     }
12527     
12528     if (gameMode == EditGame || gameMode == EndOfGame) {
12529         whiteTimeRemaining = timeRemaining[0][currentMove];
12530         blackTimeRemaining = timeRemaining[1][currentMove];
12531     }
12532     DisplayBothClocks();
12533     DisplayMove(currentMove - 1);
12534     DrawPosition(FALSE, boards[currentMove]);
12535     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12536     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12537         DisplayComment(currentMove - 1, commentList[currentMove]);
12538     }
12539 }
12540
12541
12542 void
12543 ForwardEvent()
12544 {
12545     if (gameMode == IcsExamining && !pausing) {
12546         SendToICS(ics_prefix);
12547         SendToICS("forward\n");
12548     } else {
12549         ForwardInner(currentMove + 1);
12550     }
12551 }
12552
12553 void
12554 ToEndEvent()
12555 {
12556     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12557         /* to optimze, we temporarily turn off analysis mode while we feed
12558          * the remaining moves to the engine. Otherwise we get analysis output
12559          * after each move.
12560          */ 
12561         if (first.analysisSupport) {
12562           SendToProgram("exit\nforce\n", &first);
12563           first.analyzing = FALSE;
12564         }
12565     }
12566         
12567     if (gameMode == IcsExamining && !pausing) {
12568         SendToICS(ics_prefix);
12569         SendToICS("forward 999999\n");
12570     } else {
12571         ForwardInner(forwardMostMove);
12572     }
12573
12574     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12575         /* we have fed all the moves, so reactivate analysis mode */
12576         SendToProgram("analyze\n", &first);
12577         first.analyzing = TRUE;
12578         /*first.maybeThinking = TRUE;*/
12579         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12580     }
12581 }
12582
12583 void
12584 BackwardInner(target)
12585      int target;
12586 {
12587     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12588
12589     if (appData.debugMode)
12590         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12591                 target, currentMove, forwardMostMove);
12592
12593     if (gameMode == EditPosition) return;
12594     if (currentMove <= backwardMostMove) {
12595         ClearHighlights();
12596         DrawPosition(full_redraw, boards[currentMove]);
12597         return;
12598     }
12599     if (gameMode == PlayFromGameFile && !pausing)
12600       PauseEvent();
12601     
12602     if (moveList[target][0]) {
12603         int fromX, fromY, toX, toY;
12604         toX = moveList[target][2] - AAA;
12605         toY = moveList[target][3] - ONE;
12606         if (moveList[target][1] == '@') {
12607             if (appData.highlightLastMove) {
12608                 SetHighlights(-1, -1, toX, toY);
12609             }
12610         } else {
12611             fromX = moveList[target][0] - AAA;
12612             fromY = moveList[target][1] - ONE;
12613             if (target == currentMove - 1) {
12614                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12615             }
12616             if (appData.highlightLastMove) {
12617                 SetHighlights(fromX, fromY, toX, toY);
12618             }
12619         }
12620     }
12621     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12622         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12623         while (currentMove > target) {
12624             SendToProgram("undo\n", &first);
12625             currentMove--;
12626         }
12627     } else {
12628         currentMove = target;
12629     }
12630     
12631     if (gameMode == EditGame || gameMode == EndOfGame) {
12632         whiteTimeRemaining = timeRemaining[0][currentMove];
12633         blackTimeRemaining = timeRemaining[1][currentMove];
12634     }
12635     DisplayBothClocks();
12636     DisplayMove(currentMove - 1);
12637     DrawPosition(full_redraw, boards[currentMove]);
12638     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12639     // [HGM] PV info: routine tests if comment empty
12640     DisplayComment(currentMove - 1, commentList[currentMove]);
12641 }
12642
12643 void
12644 BackwardEvent()
12645 {
12646     if (gameMode == IcsExamining && !pausing) {
12647         SendToICS(ics_prefix);
12648         SendToICS("backward\n");
12649     } else {
12650         BackwardInner(currentMove - 1);
12651     }
12652 }
12653
12654 void
12655 ToStartEvent()
12656 {
12657     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12658         /* to optimize, we temporarily turn off analysis mode while we undo
12659          * all the moves. Otherwise we get analysis output after each undo.
12660          */ 
12661         if (first.analysisSupport) {
12662           SendToProgram("exit\nforce\n", &first);
12663           first.analyzing = FALSE;
12664         }
12665     }
12666
12667     if (gameMode == IcsExamining && !pausing) {
12668         SendToICS(ics_prefix);
12669         SendToICS("backward 999999\n");
12670     } else {
12671         BackwardInner(backwardMostMove);
12672     }
12673
12674     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12675         /* we have fed all the moves, so reactivate analysis mode */
12676         SendToProgram("analyze\n", &first);
12677         first.analyzing = TRUE;
12678         /*first.maybeThinking = TRUE;*/
12679         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12680     }
12681 }
12682
12683 void
12684 ToNrEvent(int to)
12685 {
12686   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12687   if (to >= forwardMostMove) to = forwardMostMove;
12688   if (to <= backwardMostMove) to = backwardMostMove;
12689   if (to < currentMove) {
12690     BackwardInner(to);
12691   } else {
12692     ForwardInner(to);
12693   }
12694 }
12695
12696 void
12697 RevertEvent(Boolean annotate)
12698 {
12699     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12700         return;
12701     }
12702     if (gameMode != IcsExamining) {
12703         DisplayError(_("You are not examining a game"), 0);
12704         return;
12705     }
12706     if (pausing) {
12707         DisplayError(_("You can't revert while pausing"), 0);
12708         return;
12709     }
12710     SendToICS(ics_prefix);
12711     SendToICS("revert\n");
12712 }
12713
12714 void
12715 RetractMoveEvent()
12716 {
12717     switch (gameMode) {
12718       case MachinePlaysWhite:
12719       case MachinePlaysBlack:
12720         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12721             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12722             return;
12723         }
12724         if (forwardMostMove < 2) return;
12725         currentMove = forwardMostMove = forwardMostMove - 2;
12726         whiteTimeRemaining = timeRemaining[0][currentMove];
12727         blackTimeRemaining = timeRemaining[1][currentMove];
12728         DisplayBothClocks();
12729         DisplayMove(currentMove - 1);
12730         ClearHighlights();/*!! could figure this out*/
12731         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12732         SendToProgram("remove\n", &first);
12733         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12734         break;
12735
12736       case BeginningOfGame:
12737       default:
12738         break;
12739
12740       case IcsPlayingWhite:
12741       case IcsPlayingBlack:
12742         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12743             SendToICS(ics_prefix);
12744             SendToICS("takeback 2\n");
12745         } else {
12746             SendToICS(ics_prefix);
12747             SendToICS("takeback 1\n");
12748         }
12749         break;
12750     }
12751 }
12752
12753 void
12754 MoveNowEvent()
12755 {
12756     ChessProgramState *cps;
12757
12758     switch (gameMode) {
12759       case MachinePlaysWhite:
12760         if (!WhiteOnMove(forwardMostMove)) {
12761             DisplayError(_("It is your turn"), 0);
12762             return;
12763         }
12764         cps = &first;
12765         break;
12766       case MachinePlaysBlack:
12767         if (WhiteOnMove(forwardMostMove)) {
12768             DisplayError(_("It is your turn"), 0);
12769             return;
12770         }
12771         cps = &first;
12772         break;
12773       case TwoMachinesPlay:
12774         if (WhiteOnMove(forwardMostMove) ==
12775             (first.twoMachinesColor[0] == 'w')) {
12776             cps = &first;
12777         } else {
12778             cps = &second;
12779         }
12780         break;
12781       case BeginningOfGame:
12782       default:
12783         return;
12784     }
12785     SendToProgram("?\n", cps);
12786 }
12787
12788 void
12789 TruncateGameEvent()
12790 {
12791     EditGameEvent();
12792     if (gameMode != EditGame) return;
12793     TruncateGame();
12794 }
12795
12796 void
12797 TruncateGame()
12798 {
12799     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12800     if (forwardMostMove > currentMove) {
12801         if (gameInfo.resultDetails != NULL) {
12802             free(gameInfo.resultDetails);
12803             gameInfo.resultDetails = NULL;
12804             gameInfo.result = GameUnfinished;
12805         }
12806         forwardMostMove = currentMove;
12807         HistorySet(parseList, backwardMostMove, forwardMostMove,
12808                    currentMove-1);
12809     }
12810 }
12811
12812 void
12813 HintEvent()
12814 {
12815     if (appData.noChessProgram) return;
12816     switch (gameMode) {
12817       case MachinePlaysWhite:
12818         if (WhiteOnMove(forwardMostMove)) {
12819             DisplayError(_("Wait until your turn"), 0);
12820             return;
12821         }
12822         break;
12823       case BeginningOfGame:
12824       case MachinePlaysBlack:
12825         if (!WhiteOnMove(forwardMostMove)) {
12826             DisplayError(_("Wait until your turn"), 0);
12827             return;
12828         }
12829         break;
12830       default:
12831         DisplayError(_("No hint available"), 0);
12832         return;
12833     }
12834     SendToProgram("hint\n", &first);
12835     hintRequested = TRUE;
12836 }
12837
12838 void
12839 BookEvent()
12840 {
12841     if (appData.noChessProgram) return;
12842     switch (gameMode) {
12843       case MachinePlaysWhite:
12844         if (WhiteOnMove(forwardMostMove)) {
12845             DisplayError(_("Wait until your turn"), 0);
12846             return;
12847         }
12848         break;
12849       case BeginningOfGame:
12850       case MachinePlaysBlack:
12851         if (!WhiteOnMove(forwardMostMove)) {
12852             DisplayError(_("Wait until your turn"), 0);
12853             return;
12854         }
12855         break;
12856       case EditPosition:
12857         EditPositionDone(TRUE);
12858         break;
12859       case TwoMachinesPlay:
12860         return;
12861       default:
12862         break;
12863     }
12864     SendToProgram("bk\n", &first);
12865     bookOutput[0] = NULLCHAR;
12866     bookRequested = TRUE;
12867 }
12868
12869 void
12870 AboutGameEvent()
12871 {
12872     char *tags = PGNTags(&gameInfo);
12873     TagsPopUp(tags, CmailMsg());
12874     free(tags);
12875 }
12876
12877 /* end button procedures */
12878
12879 void
12880 PrintPosition(fp, move)
12881      FILE *fp;
12882      int move;
12883 {
12884     int i, j;
12885     
12886     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12887         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12888             char c = PieceToChar(boards[move][i][j]);
12889             fputc(c == 'x' ? '.' : c, fp);
12890             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12891         }
12892     }
12893     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12894       fprintf(fp, "white to play\n");
12895     else
12896       fprintf(fp, "black to play\n");
12897 }
12898
12899 void
12900 PrintOpponents(fp)
12901      FILE *fp;
12902 {
12903     if (gameInfo.white != NULL) {
12904         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12905     } else {
12906         fprintf(fp, "\n");
12907     }
12908 }
12909
12910 /* Find last component of program's own name, using some heuristics */
12911 void
12912 TidyProgramName(prog, host, buf)
12913      char *prog, *host, buf[MSG_SIZ];
12914 {
12915     char *p, *q;
12916     int local = (strcmp(host, "localhost") == 0);
12917     while (!local && (p = strchr(prog, ';')) != NULL) {
12918         p++;
12919         while (*p == ' ') p++;
12920         prog = p;
12921     }
12922     if (*prog == '"' || *prog == '\'') {
12923         q = strchr(prog + 1, *prog);
12924     } else {
12925         q = strchr(prog, ' ');
12926     }
12927     if (q == NULL) q = prog + strlen(prog);
12928     p = q;
12929     while (p >= prog && *p != '/' && *p != '\\') p--;
12930     p++;
12931     if(p == prog && *p == '"') p++;
12932     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12933     memcpy(buf, p, q - p);
12934     buf[q - p] = NULLCHAR;
12935     if (!local) {
12936         strcat(buf, "@");
12937         strcat(buf, host);
12938     }
12939 }
12940
12941 char *
12942 TimeControlTagValue()
12943 {
12944     char buf[MSG_SIZ];
12945     if (!appData.clockMode) {
12946         strcpy(buf, "-");
12947     } else if (movesPerSession > 0) {
12948         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12949     } else if (timeIncrement == 0) {
12950         sprintf(buf, "%ld", timeControl/1000);
12951     } else {
12952         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12953     }
12954     return StrSave(buf);
12955 }
12956
12957 void
12958 SetGameInfo()
12959 {
12960     /* This routine is used only for certain modes */
12961     VariantClass v = gameInfo.variant;
12962     ChessMove r = GameUnfinished;
12963     char *p = NULL;
12964
12965     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12966         r = gameInfo.result; 
12967         p = gameInfo.resultDetails; 
12968         gameInfo.resultDetails = NULL;
12969     }
12970     ClearGameInfo(&gameInfo);
12971     gameInfo.variant = v;
12972
12973     switch (gameMode) {
12974       case MachinePlaysWhite:
12975         gameInfo.event = StrSave( appData.pgnEventHeader );
12976         gameInfo.site = StrSave(HostName());
12977         gameInfo.date = PGNDate();
12978         gameInfo.round = StrSave("-");
12979         gameInfo.white = StrSave(first.tidy);
12980         gameInfo.black = StrSave(UserName());
12981         gameInfo.timeControl = TimeControlTagValue();
12982         break;
12983
12984       case MachinePlaysBlack:
12985         gameInfo.event = StrSave( appData.pgnEventHeader );
12986         gameInfo.site = StrSave(HostName());
12987         gameInfo.date = PGNDate();
12988         gameInfo.round = StrSave("-");
12989         gameInfo.white = StrSave(UserName());
12990         gameInfo.black = StrSave(first.tidy);
12991         gameInfo.timeControl = TimeControlTagValue();
12992         break;
12993
12994       case TwoMachinesPlay:
12995         gameInfo.event = StrSave( appData.pgnEventHeader );
12996         gameInfo.site = StrSave(HostName());
12997         gameInfo.date = PGNDate();
12998         if (matchGame > 0) {
12999             char buf[MSG_SIZ];
13000             sprintf(buf, "%d", matchGame);
13001             gameInfo.round = StrSave(buf);
13002         } else {
13003             gameInfo.round = StrSave("-");
13004         }
13005         if (first.twoMachinesColor[0] == 'w') {
13006             gameInfo.white = StrSave(first.tidy);
13007             gameInfo.black = StrSave(second.tidy);
13008         } else {
13009             gameInfo.white = StrSave(second.tidy);
13010             gameInfo.black = StrSave(first.tidy);
13011         }
13012         gameInfo.timeControl = TimeControlTagValue();
13013         break;
13014
13015       case EditGame:
13016         gameInfo.event = StrSave("Edited game");
13017         gameInfo.site = StrSave(HostName());
13018         gameInfo.date = PGNDate();
13019         gameInfo.round = StrSave("-");
13020         gameInfo.white = StrSave("-");
13021         gameInfo.black = StrSave("-");
13022         gameInfo.result = r;
13023         gameInfo.resultDetails = p;
13024         break;
13025
13026       case EditPosition:
13027         gameInfo.event = StrSave("Edited position");
13028         gameInfo.site = StrSave(HostName());
13029         gameInfo.date = PGNDate();
13030         gameInfo.round = StrSave("-");
13031         gameInfo.white = StrSave("-");
13032         gameInfo.black = StrSave("-");
13033         break;
13034
13035       case IcsPlayingWhite:
13036       case IcsPlayingBlack:
13037       case IcsObserving:
13038       case IcsExamining:
13039         break;
13040
13041       case PlayFromGameFile:
13042         gameInfo.event = StrSave("Game from non-PGN file");
13043         gameInfo.site = StrSave(HostName());
13044         gameInfo.date = PGNDate();
13045         gameInfo.round = StrSave("-");
13046         gameInfo.white = StrSave("?");
13047         gameInfo.black = StrSave("?");
13048         break;
13049
13050       default:
13051         break;
13052     }
13053 }
13054
13055 void
13056 ReplaceComment(index, text)
13057      int index;
13058      char *text;
13059 {
13060     int len;
13061
13062     while (*text == '\n') text++;
13063     len = strlen(text);
13064     while (len > 0 && text[len - 1] == '\n') len--;
13065
13066     if (commentList[index] != NULL)
13067       free(commentList[index]);
13068
13069     if (len == 0) {
13070         commentList[index] = NULL;
13071         return;
13072     }
13073   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13074       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13075       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13076     commentList[index] = (char *) malloc(len + 2);
13077     strncpy(commentList[index], text, len);
13078     commentList[index][len] = '\n';
13079     commentList[index][len + 1] = NULLCHAR;
13080   } else { 
13081     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13082     char *p;
13083     commentList[index] = (char *) malloc(len + 6);
13084     strcpy(commentList[index], "{\n");
13085     strncpy(commentList[index]+2, text, len);
13086     commentList[index][len+2] = NULLCHAR;
13087     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13088     strcat(commentList[index], "\n}\n");
13089   }
13090 }
13091
13092 void
13093 CrushCRs(text)
13094      char *text;
13095 {
13096   char *p = text;
13097   char *q = text;
13098   char ch;
13099
13100   do {
13101     ch = *p++;
13102     if (ch == '\r') continue;
13103     *q++ = ch;
13104   } while (ch != '\0');
13105 }
13106
13107 void
13108 AppendComment(index, text, addBraces)
13109      int index;
13110      char *text;
13111      Boolean addBraces; // [HGM] braces: tells if we should add {}
13112 {
13113     int oldlen, len;
13114     char *old;
13115
13116 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13117     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13118
13119     CrushCRs(text);
13120     while (*text == '\n') text++;
13121     len = strlen(text);
13122     while (len > 0 && text[len - 1] == '\n') len--;
13123
13124     if (len == 0) return;
13125
13126     if (commentList[index] != NULL) {
13127         old = commentList[index];
13128         oldlen = strlen(old);
13129         while(commentList[index][oldlen-1] ==  '\n')
13130           commentList[index][--oldlen] = NULLCHAR;
13131         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13132         strcpy(commentList[index], old);
13133         free(old);
13134         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13135         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13136           if(addBraces) addBraces = FALSE; else { text++; len--; }
13137           while (*text == '\n') { text++; len--; }
13138           commentList[index][--oldlen] = NULLCHAR;
13139       }
13140         if(addBraces) strcat(commentList[index], "\n{\n");
13141         else          strcat(commentList[index], "\n");
13142         strcat(commentList[index], text);
13143         if(addBraces) strcat(commentList[index], "\n}\n");
13144         else          strcat(commentList[index], "\n");
13145     } else {
13146         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13147         if(addBraces)
13148              strcpy(commentList[index], "{\n");
13149         else commentList[index][0] = NULLCHAR;
13150         strcat(commentList[index], text);
13151         strcat(commentList[index], "\n");
13152         if(addBraces) strcat(commentList[index], "}\n");
13153     }
13154 }
13155
13156 static char * FindStr( char * text, char * sub_text )
13157 {
13158     char * result = strstr( text, sub_text );
13159
13160     if( result != NULL ) {
13161         result += strlen( sub_text );
13162     }
13163
13164     return result;
13165 }
13166
13167 /* [AS] Try to extract PV info from PGN comment */
13168 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13169 char *GetInfoFromComment( int index, char * text )
13170 {
13171     char * sep = text;
13172
13173     if( text != NULL && index > 0 ) {
13174         int score = 0;
13175         int depth = 0;
13176         int time = -1, sec = 0, deci;
13177         char * s_eval = FindStr( text, "[%eval " );
13178         char * s_emt = FindStr( text, "[%emt " );
13179
13180         if( s_eval != NULL || s_emt != NULL ) {
13181             /* New style */
13182             char delim;
13183
13184             if( s_eval != NULL ) {
13185                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13186                     return text;
13187                 }
13188
13189                 if( delim != ']' ) {
13190                     return text;
13191                 }
13192             }
13193
13194             if( s_emt != NULL ) {
13195             }
13196                 return text;
13197         }
13198         else {
13199             /* We expect something like: [+|-]nnn.nn/dd */
13200             int score_lo = 0;
13201
13202             if(*text != '{') return text; // [HGM] braces: must be normal comment
13203
13204             sep = strchr( text, '/' );
13205             if( sep == NULL || sep < (text+4) ) {
13206                 return text;
13207             }
13208
13209             time = -1; sec = -1; deci = -1;
13210             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13211                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13212                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13213                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13214                 return text;
13215             }
13216
13217             if( score_lo < 0 || score_lo >= 100 ) {
13218                 return text;
13219             }
13220
13221             if(sec >= 0) time = 600*time + 10*sec; else
13222             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13223
13224             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13225
13226             /* [HGM] PV time: now locate end of PV info */
13227             while( *++sep >= '0' && *sep <= '9'); // strip depth
13228             if(time >= 0)
13229             while( *++sep >= '0' && *sep <= '9'); // strip time
13230             if(sec >= 0)
13231             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13232             if(deci >= 0)
13233             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13234             while(*sep == ' ') sep++;
13235         }
13236
13237         if( depth <= 0 ) {
13238             return text;
13239         }
13240
13241         if( time < 0 ) {
13242             time = -1;
13243         }
13244
13245         pvInfoList[index-1].depth = depth;
13246         pvInfoList[index-1].score = score;
13247         pvInfoList[index-1].time  = 10*time; // centi-sec
13248         if(*sep == '}') *sep = 0; else *--sep = '{';
13249     }
13250     return sep;
13251 }
13252
13253 void
13254 SendToProgram(message, cps)
13255      char *message;
13256      ChessProgramState *cps;
13257 {
13258     int count, outCount, error;
13259     char buf[MSG_SIZ];
13260
13261     if (cps->pr == NULL) return;
13262     Attention(cps);
13263     
13264     if (appData.debugMode) {
13265         TimeMark now;
13266         GetTimeMark(&now);
13267         fprintf(debugFP, "%ld >%-6s: %s", 
13268                 SubtractTimeMarks(&now, &programStartTime),
13269                 cps->which, message);
13270     }
13271     
13272     count = strlen(message);
13273     outCount = OutputToProcess(cps->pr, message, count, &error);
13274     if (outCount < count && !exiting 
13275                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13276         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13277         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13278             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13279                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13280                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13281             } else {
13282                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13283             }
13284             gameInfo.resultDetails = StrSave(buf);
13285         }
13286         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13287     }
13288 }
13289
13290 void
13291 ReceiveFromProgram(isr, closure, message, count, error)
13292      InputSourceRef isr;
13293      VOIDSTAR closure;
13294      char *message;
13295      int count;
13296      int error;
13297 {
13298     char *end_str;
13299     char buf[MSG_SIZ];
13300     ChessProgramState *cps = (ChessProgramState *)closure;
13301
13302     if (isr != cps->isr) return; /* Killed intentionally */
13303     if (count <= 0) {
13304         if (count == 0) {
13305             sprintf(buf,
13306                     _("Error: %s chess program (%s) exited unexpectedly"),
13307                     cps->which, cps->program);
13308         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13309                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13310                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13311                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13312                 } else {
13313                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13314                 }
13315                 gameInfo.resultDetails = StrSave(buf);
13316             }
13317             RemoveInputSource(cps->isr);
13318             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13319         } else {
13320             sprintf(buf,
13321                     _("Error reading from %s chess program (%s)"),
13322                     cps->which, cps->program);
13323             RemoveInputSource(cps->isr);
13324
13325             /* [AS] Program is misbehaving badly... kill it */
13326             if( count == -2 ) {
13327                 DestroyChildProcess( cps->pr, 9 );
13328                 cps->pr = NoProc;
13329             }
13330
13331             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13332         }
13333         return;
13334     }
13335     
13336     if ((end_str = strchr(message, '\r')) != NULL)
13337       *end_str = NULLCHAR;
13338     if ((end_str = strchr(message, '\n')) != NULL)
13339       *end_str = NULLCHAR;
13340     
13341     if (appData.debugMode) {
13342         TimeMark now; int print = 1;
13343         char *quote = ""; char c; int i;
13344
13345         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13346                 char start = message[0];
13347                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13348                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13349                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13350                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13351                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13352                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13353                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13354                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13355                         { quote = "# "; print = (appData.engineComments == 2); }
13356                 message[0] = start; // restore original message
13357         }
13358         if(print) {
13359                 GetTimeMark(&now);
13360                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13361                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13362                         quote,
13363                         message);
13364         }
13365     }
13366
13367     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13368     if (appData.icsEngineAnalyze) {
13369         if (strstr(message, "whisper") != NULL ||
13370              strstr(message, "kibitz") != NULL || 
13371             strstr(message, "tellics") != NULL) return;
13372     }
13373
13374     HandleMachineMove(message, cps);
13375 }
13376
13377
13378 void
13379 SendTimeControl(cps, mps, tc, inc, sd, st)
13380      ChessProgramState *cps;
13381      int mps, inc, sd, st;
13382      long tc;
13383 {
13384     char buf[MSG_SIZ];
13385     int seconds;
13386
13387     if( timeControl_2 > 0 ) {
13388         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13389             tc = timeControl_2;
13390         }
13391     }
13392     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13393     inc /= cps->timeOdds;
13394     st  /= cps->timeOdds;
13395
13396     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13397
13398     if (st > 0) {
13399       /* Set exact time per move, normally using st command */
13400       if (cps->stKludge) {
13401         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13402         seconds = st % 60;
13403         if (seconds == 0) {
13404           sprintf(buf, "level 1 %d\n", st/60);
13405         } else {
13406           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13407         }
13408       } else {
13409         sprintf(buf, "st %d\n", st);
13410       }
13411     } else {
13412       /* Set conventional or incremental time control, using level command */
13413       if (seconds == 0) {
13414         /* Note old gnuchess bug -- minutes:seconds used to not work.
13415            Fixed in later versions, but still avoid :seconds
13416            when seconds is 0. */
13417         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13418       } else {
13419         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13420                 seconds, inc/1000);
13421       }
13422     }
13423     SendToProgram(buf, cps);
13424
13425     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13426     /* Orthogonally, limit search to given depth */
13427     if (sd > 0) {
13428       if (cps->sdKludge) {
13429         sprintf(buf, "depth\n%d\n", sd);
13430       } else {
13431         sprintf(buf, "sd %d\n", sd);
13432       }
13433       SendToProgram(buf, cps);
13434     }
13435
13436     if(cps->nps > 0) { /* [HGM] nps */
13437         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13438         else {
13439                 sprintf(buf, "nps %d\n", cps->nps);
13440               SendToProgram(buf, cps);
13441         }
13442     }
13443 }
13444
13445 ChessProgramState *WhitePlayer()
13446 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13447 {
13448     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13449        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13450         return &second;
13451     return &first;
13452 }
13453
13454 void
13455 SendTimeRemaining(cps, machineWhite)
13456      ChessProgramState *cps;
13457      int /*boolean*/ machineWhite;
13458 {
13459     char message[MSG_SIZ];
13460     long time, otime;
13461
13462     /* Note: this routine must be called when the clocks are stopped
13463        or when they have *just* been set or switched; otherwise
13464        it will be off by the time since the current tick started.
13465     */
13466     if (machineWhite) {
13467         time = whiteTimeRemaining / 10;
13468         otime = blackTimeRemaining / 10;
13469     } else {
13470         time = blackTimeRemaining / 10;
13471         otime = whiteTimeRemaining / 10;
13472     }
13473     /* [HGM] translate opponent's time by time-odds factor */
13474     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13475     if (appData.debugMode) {
13476         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13477     }
13478
13479     if (time <= 0) time = 1;
13480     if (otime <= 0) otime = 1;
13481     
13482     sprintf(message, "time %ld\n", time);
13483     SendToProgram(message, cps);
13484
13485     sprintf(message, "otim %ld\n", otime);
13486     SendToProgram(message, cps);
13487 }
13488
13489 int
13490 BoolFeature(p, name, loc, cps)
13491      char **p;
13492      char *name;
13493      int *loc;
13494      ChessProgramState *cps;
13495 {
13496   char buf[MSG_SIZ];
13497   int len = strlen(name);
13498   int val;
13499   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13500     (*p) += len + 1;
13501     sscanf(*p, "%d", &val);
13502     *loc = (val != 0);
13503     while (**p && **p != ' ') (*p)++;
13504     sprintf(buf, "accepted %s\n", name);
13505     SendToProgram(buf, cps);
13506     return TRUE;
13507   }
13508   return FALSE;
13509 }
13510
13511 int
13512 IntFeature(p, name, loc, cps)
13513      char **p;
13514      char *name;
13515      int *loc;
13516      ChessProgramState *cps;
13517 {
13518   char buf[MSG_SIZ];
13519   int len = strlen(name);
13520   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13521     (*p) += len + 1;
13522     sscanf(*p, "%d", loc);
13523     while (**p && **p != ' ') (*p)++;
13524     sprintf(buf, "accepted %s\n", name);
13525     SendToProgram(buf, cps);
13526     return TRUE;
13527   }
13528   return FALSE;
13529 }
13530
13531 int
13532 StringFeature(p, name, loc, cps)
13533      char **p;
13534      char *name;
13535      char loc[];
13536      ChessProgramState *cps;
13537 {
13538   char buf[MSG_SIZ];
13539   int len = strlen(name);
13540   if (strncmp((*p), name, len) == 0
13541       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13542     (*p) += len + 2;
13543     sscanf(*p, "%[^\"]", loc);
13544     while (**p && **p != '\"') (*p)++;
13545     if (**p == '\"') (*p)++;
13546     sprintf(buf, "accepted %s\n", name);
13547     SendToProgram(buf, cps);
13548     return TRUE;
13549   }
13550   return FALSE;
13551 }
13552
13553 int 
13554 ParseOption(Option *opt, ChessProgramState *cps)
13555 // [HGM] options: process the string that defines an engine option, and determine
13556 // name, type, default value, and allowed value range
13557 {
13558         char *p, *q, buf[MSG_SIZ];
13559         int n, min = (-1)<<31, max = 1<<31, def;
13560
13561         if(p = strstr(opt->name, " -spin ")) {
13562             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13563             if(max < min) max = min; // enforce consistency
13564             if(def < min) def = min;
13565             if(def > max) def = max;
13566             opt->value = def;
13567             opt->min = min;
13568             opt->max = max;
13569             opt->type = Spin;
13570         } else if((p = strstr(opt->name, " -slider "))) {
13571             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13572             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13573             if(max < min) max = min; // enforce consistency
13574             if(def < min) def = min;
13575             if(def > max) def = max;
13576             opt->value = def;
13577             opt->min = min;
13578             opt->max = max;
13579             opt->type = Spin; // Slider;
13580         } else if((p = strstr(opt->name, " -string "))) {
13581             opt->textValue = p+9;
13582             opt->type = TextBox;
13583         } else if((p = strstr(opt->name, " -file "))) {
13584             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13585             opt->textValue = p+7;
13586             opt->type = TextBox; // FileName;
13587         } else if((p = strstr(opt->name, " -path "))) {
13588             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13589             opt->textValue = p+7;
13590             opt->type = TextBox; // PathName;
13591         } else if(p = strstr(opt->name, " -check ")) {
13592             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13593             opt->value = (def != 0);
13594             opt->type = CheckBox;
13595         } else if(p = strstr(opt->name, " -combo ")) {
13596             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13597             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13598             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13599             opt->value = n = 0;
13600             while(q = StrStr(q, " /// ")) {
13601                 n++; *q = 0;    // count choices, and null-terminate each of them
13602                 q += 5;
13603                 if(*q == '*') { // remember default, which is marked with * prefix
13604                     q++;
13605                     opt->value = n;
13606                 }
13607                 cps->comboList[cps->comboCnt++] = q;
13608             }
13609             cps->comboList[cps->comboCnt++] = NULL;
13610             opt->max = n + 1;
13611             opt->type = ComboBox;
13612         } else if(p = strstr(opt->name, " -button")) {
13613             opt->type = Button;
13614         } else if(p = strstr(opt->name, " -save")) {
13615             opt->type = SaveButton;
13616         } else return FALSE;
13617         *p = 0; // terminate option name
13618         // now look if the command-line options define a setting for this engine option.
13619         if(cps->optionSettings && cps->optionSettings[0])
13620             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13621         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13622                 sprintf(buf, "option %s", p);
13623                 if(p = strstr(buf, ",")) *p = 0;
13624                 strcat(buf, "\n");
13625                 SendToProgram(buf, cps);
13626         }
13627         return TRUE;
13628 }
13629
13630 void
13631 FeatureDone(cps, val)
13632      ChessProgramState* cps;
13633      int val;
13634 {
13635   DelayedEventCallback cb = GetDelayedEvent();
13636   if ((cb == InitBackEnd3 && cps == &first) ||
13637       (cb == TwoMachinesEventIfReady && cps == &second)) {
13638     CancelDelayedEvent();
13639     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13640   }
13641   cps->initDone = val;
13642 }
13643
13644 /* Parse feature command from engine */
13645 void
13646 ParseFeatures(args, cps)
13647      char* args;
13648      ChessProgramState *cps;  
13649 {
13650   char *p = args;
13651   char *q;
13652   int val;
13653   char buf[MSG_SIZ];
13654
13655   for (;;) {
13656     while (*p == ' ') p++;
13657     if (*p == NULLCHAR) return;
13658
13659     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13660     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13661     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13662     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13663     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13664     if (BoolFeature(&p, "reuse", &val, cps)) {
13665       /* Engine can disable reuse, but can't enable it if user said no */
13666       if (!val) cps->reuse = FALSE;
13667       continue;
13668     }
13669     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13670     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13671       if (gameMode == TwoMachinesPlay) {
13672         DisplayTwoMachinesTitle();
13673       } else {
13674         DisplayTitle("");
13675       }
13676       continue;
13677     }
13678     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13679     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13680     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13681     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13682     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13683     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13684     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13685     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13686     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13687     if (IntFeature(&p, "done", &val, cps)) {
13688       FeatureDone(cps, val);
13689       continue;
13690     }
13691     /* Added by Tord: */
13692     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13693     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13694     /* End of additions by Tord */
13695
13696     /* [HGM] added features: */
13697     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13698     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13699     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13700     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13701     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13702     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13703     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13704         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13705             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13706             SendToProgram(buf, cps);
13707             continue;
13708         }
13709         if(cps->nrOptions >= MAX_OPTIONS) {
13710             cps->nrOptions--;
13711             sprintf(buf, "%s engine has too many options\n", cps->which);
13712             DisplayError(buf, 0);
13713         }
13714         continue;
13715     }
13716     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13717     /* End of additions by HGM */
13718
13719     /* unknown feature: complain and skip */
13720     q = p;
13721     while (*q && *q != '=') q++;
13722     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13723     SendToProgram(buf, cps);
13724     p = q;
13725     if (*p == '=') {
13726       p++;
13727       if (*p == '\"') {
13728         p++;
13729         while (*p && *p != '\"') p++;
13730         if (*p == '\"') p++;
13731       } else {
13732         while (*p && *p != ' ') p++;
13733       }
13734     }
13735   }
13736
13737 }
13738
13739 void
13740 PeriodicUpdatesEvent(newState)
13741      int newState;
13742 {
13743     if (newState == appData.periodicUpdates)
13744       return;
13745
13746     appData.periodicUpdates=newState;
13747
13748     /* Display type changes, so update it now */
13749 //    DisplayAnalysis();
13750
13751     /* Get the ball rolling again... */
13752     if (newState) {
13753         AnalysisPeriodicEvent(1);
13754         StartAnalysisClock();
13755     }
13756 }
13757
13758 void
13759 PonderNextMoveEvent(newState)
13760      int newState;
13761 {
13762     if (newState == appData.ponderNextMove) return;
13763     if (gameMode == EditPosition) EditPositionDone(TRUE);
13764     if (newState) {
13765         SendToProgram("hard\n", &first);
13766         if (gameMode == TwoMachinesPlay) {
13767             SendToProgram("hard\n", &second);
13768         }
13769     } else {
13770         SendToProgram("easy\n", &first);
13771         thinkOutput[0] = NULLCHAR;
13772         if (gameMode == TwoMachinesPlay) {
13773             SendToProgram("easy\n", &second);
13774         }
13775     }
13776     appData.ponderNextMove = newState;
13777 }
13778
13779 void
13780 NewSettingEvent(option, command, value)
13781      char *command;
13782      int option, value;
13783 {
13784     char buf[MSG_SIZ];
13785
13786     if (gameMode == EditPosition) EditPositionDone(TRUE);
13787     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13788     SendToProgram(buf, &first);
13789     if (gameMode == TwoMachinesPlay) {
13790         SendToProgram(buf, &second);
13791     }
13792 }
13793
13794 void
13795 ShowThinkingEvent()
13796 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13797 {
13798     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13799     int newState = appData.showThinking
13800         // [HGM] thinking: other features now need thinking output as well
13801         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13802     
13803     if (oldState == newState) return;
13804     oldState = newState;
13805     if (gameMode == EditPosition) EditPositionDone(TRUE);
13806     if (oldState) {
13807         SendToProgram("post\n", &first);
13808         if (gameMode == TwoMachinesPlay) {
13809             SendToProgram("post\n", &second);
13810         }
13811     } else {
13812         SendToProgram("nopost\n", &first);
13813         thinkOutput[0] = NULLCHAR;
13814         if (gameMode == TwoMachinesPlay) {
13815             SendToProgram("nopost\n", &second);
13816         }
13817     }
13818 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13819 }
13820
13821 void
13822 AskQuestionEvent(title, question, replyPrefix, which)
13823      char *title; char *question; char *replyPrefix; char *which;
13824 {
13825   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13826   if (pr == NoProc) return;
13827   AskQuestion(title, question, replyPrefix, pr);
13828 }
13829
13830 void
13831 DisplayMove(moveNumber)
13832      int moveNumber;
13833 {
13834     char message[MSG_SIZ];
13835     char res[MSG_SIZ];
13836     char cpThinkOutput[MSG_SIZ];
13837
13838     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13839     
13840     if (moveNumber == forwardMostMove - 1 || 
13841         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13842
13843         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13844
13845         if (strchr(cpThinkOutput, '\n')) {
13846             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13847         }
13848     } else {
13849         *cpThinkOutput = NULLCHAR;
13850     }
13851
13852     /* [AS] Hide thinking from human user */
13853     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13854         *cpThinkOutput = NULLCHAR;
13855         if( thinkOutput[0] != NULLCHAR ) {
13856             int i;
13857
13858             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13859                 cpThinkOutput[i] = '.';
13860             }
13861             cpThinkOutput[i] = NULLCHAR;
13862             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13863         }
13864     }
13865
13866     if (moveNumber == forwardMostMove - 1 &&
13867         gameInfo.resultDetails != NULL) {
13868         if (gameInfo.resultDetails[0] == NULLCHAR) {
13869             sprintf(res, " %s", PGNResult(gameInfo.result));
13870         } else {
13871             sprintf(res, " {%s} %s",
13872                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13873         }
13874     } else {
13875         res[0] = NULLCHAR;
13876     }
13877
13878     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13879         DisplayMessage(res, cpThinkOutput);
13880     } else {
13881         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13882                 WhiteOnMove(moveNumber) ? " " : ".. ",
13883                 parseList[moveNumber], res);
13884         DisplayMessage(message, cpThinkOutput);
13885     }
13886 }
13887
13888 void
13889 DisplayComment(moveNumber, text)
13890      int moveNumber;
13891      char *text;
13892 {
13893     char title[MSG_SIZ];
13894     char buf[8000]; // comment can be long!
13895     int score, depth;
13896     
13897     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13898       strcpy(title, "Comment");
13899     } else {
13900       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13901               WhiteOnMove(moveNumber) ? " " : ".. ",
13902               parseList[moveNumber]);
13903     }
13904     // [HGM] PV info: display PV info together with (or as) comment
13905     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13906       if(text == NULL) text = "";                                           
13907       score = pvInfoList[moveNumber].score;
13908       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13909               depth, (pvInfoList[moveNumber].time+50)/100, text);
13910       text = buf;
13911     }
13912     if (text != NULL && (appData.autoDisplayComment || commentUp))
13913         CommentPopUp(title, text);
13914 }
13915
13916 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13917  * might be busy thinking or pondering.  It can be omitted if your
13918  * gnuchess is configured to stop thinking immediately on any user
13919  * input.  However, that gnuchess feature depends on the FIONREAD
13920  * ioctl, which does not work properly on some flavors of Unix.
13921  */
13922 void
13923 Attention(cps)
13924      ChessProgramState *cps;
13925 {
13926 #if ATTENTION
13927     if (!cps->useSigint) return;
13928     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13929     switch (gameMode) {
13930       case MachinePlaysWhite:
13931       case MachinePlaysBlack:
13932       case TwoMachinesPlay:
13933       case IcsPlayingWhite:
13934       case IcsPlayingBlack:
13935       case AnalyzeMode:
13936       case AnalyzeFile:
13937         /* Skip if we know it isn't thinking */
13938         if (!cps->maybeThinking) return;
13939         if (appData.debugMode)
13940           fprintf(debugFP, "Interrupting %s\n", cps->which);
13941         InterruptChildProcess(cps->pr);
13942         cps->maybeThinking = FALSE;
13943         break;
13944       default:
13945         break;
13946     }
13947 #endif /*ATTENTION*/
13948 }
13949
13950 int
13951 CheckFlags()
13952 {
13953     if (whiteTimeRemaining <= 0) {
13954         if (!whiteFlag) {
13955             whiteFlag = TRUE;
13956             if (appData.icsActive) {
13957                 if (appData.autoCallFlag &&
13958                     gameMode == IcsPlayingBlack && !blackFlag) {
13959                   SendToICS(ics_prefix);
13960                   SendToICS("flag\n");
13961                 }
13962             } else {
13963                 if (blackFlag) {
13964                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13965                 } else {
13966                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13967                     if (appData.autoCallFlag) {
13968                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13969                         return TRUE;
13970                     }
13971                 }
13972             }
13973         }
13974     }
13975     if (blackTimeRemaining <= 0) {
13976         if (!blackFlag) {
13977             blackFlag = TRUE;
13978             if (appData.icsActive) {
13979                 if (appData.autoCallFlag &&
13980                     gameMode == IcsPlayingWhite && !whiteFlag) {
13981                   SendToICS(ics_prefix);
13982                   SendToICS("flag\n");
13983                 }
13984             } else {
13985                 if (whiteFlag) {
13986                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13987                 } else {
13988                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13989                     if (appData.autoCallFlag) {
13990                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13991                         return TRUE;
13992                     }
13993                 }
13994             }
13995         }
13996     }
13997     return FALSE;
13998 }
13999
14000 void
14001 CheckTimeControl()
14002 {
14003     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14004         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14005
14006     /*
14007      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14008      */
14009     if ( !WhiteOnMove(forwardMostMove) )
14010         /* White made time control */
14011         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14012         /* [HGM] time odds: correct new time quota for time odds! */
14013                                             / WhitePlayer()->timeOdds;
14014       else
14015         /* Black made time control */
14016         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14017                                             / WhitePlayer()->other->timeOdds;
14018 }
14019
14020 void
14021 DisplayBothClocks()
14022 {
14023     int wom = gameMode == EditPosition ?
14024       !blackPlaysFirst : WhiteOnMove(currentMove);
14025     DisplayWhiteClock(whiteTimeRemaining, wom);
14026     DisplayBlackClock(blackTimeRemaining, !wom);
14027 }
14028
14029
14030 /* Timekeeping seems to be a portability nightmare.  I think everyone
14031    has ftime(), but I'm really not sure, so I'm including some ifdefs
14032    to use other calls if you don't.  Clocks will be less accurate if
14033    you have neither ftime nor gettimeofday.
14034 */
14035
14036 /* VS 2008 requires the #include outside of the function */
14037 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14038 #include <sys/timeb.h>
14039 #endif
14040
14041 /* Get the current time as a TimeMark */
14042 void
14043 GetTimeMark(tm)
14044      TimeMark *tm;
14045 {
14046 #if HAVE_GETTIMEOFDAY
14047
14048     struct timeval timeVal;
14049     struct timezone timeZone;
14050
14051     gettimeofday(&timeVal, &timeZone);
14052     tm->sec = (long) timeVal.tv_sec; 
14053     tm->ms = (int) (timeVal.tv_usec / 1000L);
14054
14055 #else /*!HAVE_GETTIMEOFDAY*/
14056 #if HAVE_FTIME
14057
14058 // include <sys/timeb.h> / moved to just above start of function
14059     struct timeb timeB;
14060
14061     ftime(&timeB);
14062     tm->sec = (long) timeB.time;
14063     tm->ms = (int) timeB.millitm;
14064
14065 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14066     tm->sec = (long) time(NULL);
14067     tm->ms = 0;
14068 #endif
14069 #endif
14070 }
14071
14072 /* Return the difference in milliseconds between two
14073    time marks.  We assume the difference will fit in a long!
14074 */
14075 long
14076 SubtractTimeMarks(tm2, tm1)
14077      TimeMark *tm2, *tm1;
14078 {
14079     return 1000L*(tm2->sec - tm1->sec) +
14080            (long) (tm2->ms - tm1->ms);
14081 }
14082
14083
14084 /*
14085  * Code to manage the game clocks.
14086  *
14087  * In tournament play, black starts the clock and then white makes a move.
14088  * We give the human user a slight advantage if he is playing white---the
14089  * clocks don't run until he makes his first move, so it takes zero time.
14090  * Also, we don't account for network lag, so we could get out of sync
14091  * with GNU Chess's clock -- but then, referees are always right.  
14092  */
14093
14094 static TimeMark tickStartTM;
14095 static long intendedTickLength;
14096
14097 long
14098 NextTickLength(timeRemaining)
14099      long timeRemaining;
14100 {
14101     long nominalTickLength, nextTickLength;
14102
14103     if (timeRemaining > 0L && timeRemaining <= 10000L)
14104       nominalTickLength = 100L;
14105     else
14106       nominalTickLength = 1000L;
14107     nextTickLength = timeRemaining % nominalTickLength;
14108     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14109
14110     return nextTickLength;
14111 }
14112
14113 /* Adjust clock one minute up or down */
14114 void
14115 AdjustClock(Boolean which, int dir)
14116 {
14117     if(which) blackTimeRemaining += 60000*dir;
14118     else      whiteTimeRemaining += 60000*dir;
14119     DisplayBothClocks();
14120 }
14121
14122 /* Stop clocks and reset to a fresh time control */
14123 void
14124 ResetClocks() 
14125 {
14126     (void) StopClockTimer();
14127     if (appData.icsActive) {
14128         whiteTimeRemaining = blackTimeRemaining = 0;
14129     } else if (searchTime) {
14130         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14131         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14132     } else { /* [HGM] correct new time quote for time odds */
14133         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14134         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14135     }
14136     if (whiteFlag || blackFlag) {
14137         DisplayTitle("");
14138         whiteFlag = blackFlag = FALSE;
14139     }
14140     DisplayBothClocks();
14141 }
14142
14143 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14144
14145 /* Decrement running clock by amount of time that has passed */
14146 void
14147 DecrementClocks()
14148 {
14149     long timeRemaining;
14150     long lastTickLength, fudge;
14151     TimeMark now;
14152
14153     if (!appData.clockMode) return;
14154     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14155         
14156     GetTimeMark(&now);
14157
14158     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14159
14160     /* Fudge if we woke up a little too soon */
14161     fudge = intendedTickLength - lastTickLength;
14162     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14163
14164     if (WhiteOnMove(forwardMostMove)) {
14165         if(whiteNPS >= 0) lastTickLength = 0;
14166         timeRemaining = whiteTimeRemaining -= lastTickLength;
14167         DisplayWhiteClock(whiteTimeRemaining - fudge,
14168                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14169     } else {
14170         if(blackNPS >= 0) lastTickLength = 0;
14171         timeRemaining = blackTimeRemaining -= lastTickLength;
14172         DisplayBlackClock(blackTimeRemaining - fudge,
14173                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14174     }
14175
14176     if (CheckFlags()) return;
14177         
14178     tickStartTM = now;
14179     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14180     StartClockTimer(intendedTickLength);
14181
14182     /* if the time remaining has fallen below the alarm threshold, sound the
14183      * alarm. if the alarm has sounded and (due to a takeback or time control
14184      * with increment) the time remaining has increased to a level above the
14185      * threshold, reset the alarm so it can sound again. 
14186      */
14187     
14188     if (appData.icsActive && appData.icsAlarm) {
14189
14190         /* make sure we are dealing with the user's clock */
14191         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14192                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14193            )) return;
14194
14195         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14196             alarmSounded = FALSE;
14197         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14198             PlayAlarmSound();
14199             alarmSounded = TRUE;
14200         }
14201     }
14202 }
14203
14204
14205 /* A player has just moved, so stop the previously running
14206    clock and (if in clock mode) start the other one.
14207    We redisplay both clocks in case we're in ICS mode, because
14208    ICS gives us an update to both clocks after every move.
14209    Note that this routine is called *after* forwardMostMove
14210    is updated, so the last fractional tick must be subtracted
14211    from the color that is *not* on move now.
14212 */
14213 void
14214 SwitchClocks(int newMoveNr)
14215 {
14216     long lastTickLength;
14217     TimeMark now;
14218     int flagged = FALSE;
14219
14220     GetTimeMark(&now);
14221
14222     if (StopClockTimer() && appData.clockMode) {
14223         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14224         if (!WhiteOnMove(forwardMostMove)) {
14225             if(blackNPS >= 0) lastTickLength = 0;
14226             blackTimeRemaining -= lastTickLength;
14227            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14228 //         if(pvInfoList[forwardMostMove-1].time == -1)
14229                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14230                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14231         } else {
14232            if(whiteNPS >= 0) lastTickLength = 0;
14233            whiteTimeRemaining -= lastTickLength;
14234            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14235 //         if(pvInfoList[forwardMostMove-1].time == -1)
14236                  pvInfoList[forwardMostMove-1].time = 
14237                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14238         }
14239         flagged = CheckFlags();
14240     }
14241     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14242     CheckTimeControl();
14243
14244     if (flagged || !appData.clockMode) return;
14245
14246     switch (gameMode) {
14247       case MachinePlaysBlack:
14248       case MachinePlaysWhite:
14249       case BeginningOfGame:
14250         if (pausing) return;
14251         break;
14252
14253       case EditGame:
14254       case PlayFromGameFile:
14255       case IcsExamining:
14256         return;
14257
14258       default:
14259         break;
14260     }
14261
14262     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14263         if(WhiteOnMove(forwardMostMove))
14264              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14265         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14266     }
14267
14268     tickStartTM = now;
14269     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14270       whiteTimeRemaining : blackTimeRemaining);
14271     StartClockTimer(intendedTickLength);
14272 }
14273         
14274
14275 /* Stop both clocks */
14276 void
14277 StopClocks()
14278 {       
14279     long lastTickLength;
14280     TimeMark now;
14281
14282     if (!StopClockTimer()) return;
14283     if (!appData.clockMode) return;
14284
14285     GetTimeMark(&now);
14286
14287     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14288     if (WhiteOnMove(forwardMostMove)) {
14289         if(whiteNPS >= 0) lastTickLength = 0;
14290         whiteTimeRemaining -= lastTickLength;
14291         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14292     } else {
14293         if(blackNPS >= 0) lastTickLength = 0;
14294         blackTimeRemaining -= lastTickLength;
14295         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14296     }
14297     CheckFlags();
14298 }
14299         
14300 /* Start clock of player on move.  Time may have been reset, so
14301    if clock is already running, stop and restart it. */
14302 void
14303 StartClocks()
14304 {
14305     (void) StopClockTimer(); /* in case it was running already */
14306     DisplayBothClocks();
14307     if (CheckFlags()) return;
14308
14309     if (!appData.clockMode) return;
14310     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14311
14312     GetTimeMark(&tickStartTM);
14313     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14314       whiteTimeRemaining : blackTimeRemaining);
14315
14316    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14317     whiteNPS = blackNPS = -1; 
14318     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14319        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14320         whiteNPS = first.nps;
14321     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14322        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14323         blackNPS = first.nps;
14324     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14325         whiteNPS = second.nps;
14326     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14327         blackNPS = second.nps;
14328     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14329
14330     StartClockTimer(intendedTickLength);
14331 }
14332
14333 char *
14334 TimeString(ms)
14335      long ms;
14336 {
14337     long second, minute, hour, day;
14338     char *sign = "";
14339     static char buf[32];
14340     
14341     if (ms > 0 && ms <= 9900) {
14342       /* convert milliseconds to tenths, rounding up */
14343       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14344
14345       sprintf(buf, " %03.1f ", tenths/10.0);
14346       return buf;
14347     }
14348
14349     /* convert milliseconds to seconds, rounding up */
14350     /* use floating point to avoid strangeness of integer division
14351        with negative dividends on many machines */
14352     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14353
14354     if (second < 0) {
14355         sign = "-";
14356         second = -second;
14357     }
14358     
14359     day = second / (60 * 60 * 24);
14360     second = second % (60 * 60 * 24);
14361     hour = second / (60 * 60);
14362     second = second % (60 * 60);
14363     minute = second / 60;
14364     second = second % 60;
14365     
14366     if (day > 0)
14367       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14368               sign, day, hour, minute, second);
14369     else if (hour > 0)
14370       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14371     else
14372       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14373     
14374     return buf;
14375 }
14376
14377
14378 /*
14379  * This is necessary because some C libraries aren't ANSI C compliant yet.
14380  */
14381 char *
14382 StrStr(string, match)
14383      char *string, *match;
14384 {
14385     int i, length;
14386     
14387     length = strlen(match);
14388     
14389     for (i = strlen(string) - length; i >= 0; i--, string++)
14390       if (!strncmp(match, string, length))
14391         return string;
14392     
14393     return NULL;
14394 }
14395
14396 char *
14397 StrCaseStr(string, match)
14398      char *string, *match;
14399 {
14400     int i, j, length;
14401     
14402     length = strlen(match);
14403     
14404     for (i = strlen(string) - length; i >= 0; i--, string++) {
14405         for (j = 0; j < length; j++) {
14406             if (ToLower(match[j]) != ToLower(string[j]))
14407               break;
14408         }
14409         if (j == length) return string;
14410     }
14411
14412     return NULL;
14413 }
14414
14415 #ifndef _amigados
14416 int
14417 StrCaseCmp(s1, s2)
14418      char *s1, *s2;
14419 {
14420     char c1, c2;
14421     
14422     for (;;) {
14423         c1 = ToLower(*s1++);
14424         c2 = ToLower(*s2++);
14425         if (c1 > c2) return 1;
14426         if (c1 < c2) return -1;
14427         if (c1 == NULLCHAR) return 0;
14428     }
14429 }
14430
14431
14432 int
14433 ToLower(c)
14434      int c;
14435 {
14436     return isupper(c) ? tolower(c) : c;
14437 }
14438
14439
14440 int
14441 ToUpper(c)
14442      int c;
14443 {
14444     return islower(c) ? toupper(c) : c;
14445 }
14446 #endif /* !_amigados    */
14447
14448 char *
14449 StrSave(s)
14450      char *s;
14451 {
14452     char *ret;
14453
14454     if ((ret = (char *) malloc(strlen(s) + 1))) {
14455         strcpy(ret, s);
14456     }
14457     return ret;
14458 }
14459
14460 char *
14461 StrSavePtr(s, savePtr)
14462      char *s, **savePtr;
14463 {
14464     if (*savePtr) {
14465         free(*savePtr);
14466     }
14467     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14468         strcpy(*savePtr, s);
14469     }
14470     return(*savePtr);
14471 }
14472
14473 char *
14474 PGNDate()
14475 {
14476     time_t clock;
14477     struct tm *tm;
14478     char buf[MSG_SIZ];
14479
14480     clock = time((time_t *)NULL);
14481     tm = localtime(&clock);
14482     sprintf(buf, "%04d.%02d.%02d",
14483             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14484     return StrSave(buf);
14485 }
14486
14487
14488 char *
14489 PositionToFEN(move, overrideCastling)
14490      int move;
14491      char *overrideCastling;
14492 {
14493     int i, j, fromX, fromY, toX, toY;
14494     int whiteToPlay;
14495     char buf[128];
14496     char *p, *q;
14497     int emptycount;
14498     ChessSquare piece;
14499
14500     whiteToPlay = (gameMode == EditPosition) ?
14501       !blackPlaysFirst : (move % 2 == 0);
14502     p = buf;
14503
14504     /* Piece placement data */
14505     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14506         emptycount = 0;
14507         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14508             if (boards[move][i][j] == EmptySquare) {
14509                 emptycount++;
14510             } else { ChessSquare piece = boards[move][i][j];
14511                 if (emptycount > 0) {
14512                     if(emptycount<10) /* [HGM] can be >= 10 */
14513                         *p++ = '0' + emptycount;
14514                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14515                     emptycount = 0;
14516                 }
14517                 if(PieceToChar(piece) == '+') {
14518                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14519                     *p++ = '+';
14520                     piece = (ChessSquare)(DEMOTED piece);
14521                 } 
14522                 *p++ = PieceToChar(piece);
14523                 if(p[-1] == '~') {
14524                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14525                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14526                     *p++ = '~';
14527                 }
14528             }
14529         }
14530         if (emptycount > 0) {
14531             if(emptycount<10) /* [HGM] can be >= 10 */
14532                 *p++ = '0' + emptycount;
14533             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14534             emptycount = 0;
14535         }
14536         *p++ = '/';
14537     }
14538     *(p - 1) = ' ';
14539
14540     /* [HGM] print Crazyhouse or Shogi holdings */
14541     if( gameInfo.holdingsWidth ) {
14542         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14543         q = p;
14544         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14545             piece = boards[move][i][BOARD_WIDTH-1];
14546             if( piece != EmptySquare )
14547               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14548                   *p++ = PieceToChar(piece);
14549         }
14550         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14551             piece = boards[move][BOARD_HEIGHT-i-1][0];
14552             if( piece != EmptySquare )
14553               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14554                   *p++ = PieceToChar(piece);
14555         }
14556
14557         if( q == p ) *p++ = '-';
14558         *p++ = ']';
14559         *p++ = ' ';
14560     }
14561
14562     /* Active color */
14563     *p++ = whiteToPlay ? 'w' : 'b';
14564     *p++ = ' ';
14565
14566   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14567     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14568   } else {
14569   if(nrCastlingRights) {
14570      q = p;
14571      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14572        /* [HGM] write directly from rights */
14573            if(boards[move][CASTLING][2] != NoRights &&
14574               boards[move][CASTLING][0] != NoRights   )
14575                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14576            if(boards[move][CASTLING][2] != NoRights &&
14577               boards[move][CASTLING][1] != NoRights   )
14578                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14579            if(boards[move][CASTLING][5] != NoRights &&
14580               boards[move][CASTLING][3] != NoRights   )
14581                 *p++ = boards[move][CASTLING][3] + AAA;
14582            if(boards[move][CASTLING][5] != NoRights &&
14583               boards[move][CASTLING][4] != NoRights   )
14584                 *p++ = boards[move][CASTLING][4] + AAA;
14585      } else {
14586
14587         /* [HGM] write true castling rights */
14588         if( nrCastlingRights == 6 ) {
14589             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14590                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14591             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14592                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14593             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14594                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14595             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14596                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14597         }
14598      }
14599      if (q == p) *p++ = '-'; /* No castling rights */
14600      *p++ = ' ';
14601   }
14602
14603   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14604      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14605     /* En passant target square */
14606     if (move > backwardMostMove) {
14607         fromX = moveList[move - 1][0] - AAA;
14608         fromY = moveList[move - 1][1] - ONE;
14609         toX = moveList[move - 1][2] - AAA;
14610         toY = moveList[move - 1][3] - ONE;
14611         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14612             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14613             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14614             fromX == toX) {
14615             /* 2-square pawn move just happened */
14616             *p++ = toX + AAA;
14617             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14618         } else {
14619             *p++ = '-';
14620         }
14621     } else if(move == backwardMostMove) {
14622         // [HGM] perhaps we should always do it like this, and forget the above?
14623         if((signed char)boards[move][EP_STATUS] >= 0) {
14624             *p++ = boards[move][EP_STATUS] + AAA;
14625             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14626         } else {
14627             *p++ = '-';
14628         }
14629     } else {
14630         *p++ = '-';
14631     }
14632     *p++ = ' ';
14633   }
14634   }
14635
14636     /* [HGM] find reversible plies */
14637     {   int i = 0, j=move;
14638
14639         if (appData.debugMode) { int k;
14640             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14641             for(k=backwardMostMove; k<=forwardMostMove; k++)
14642                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14643
14644         }
14645
14646         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14647         if( j == backwardMostMove ) i += initialRulePlies;
14648         sprintf(p, "%d ", i);
14649         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14650     }
14651     /* Fullmove number */
14652     sprintf(p, "%d", (move / 2) + 1);
14653     
14654     return StrSave(buf);
14655 }
14656
14657 Boolean
14658 ParseFEN(board, blackPlaysFirst, fen)
14659     Board board;
14660      int *blackPlaysFirst;
14661      char *fen;
14662 {
14663     int i, j;
14664     char *p;
14665     int emptycount;
14666     ChessSquare piece;
14667
14668     p = fen;
14669
14670     /* [HGM] by default clear Crazyhouse holdings, if present */
14671     if(gameInfo.holdingsWidth) {
14672        for(i=0; i<BOARD_HEIGHT; i++) {
14673            board[i][0]             = EmptySquare; /* black holdings */
14674            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14675            board[i][1]             = (ChessSquare) 0; /* black counts */
14676            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14677        }
14678     }
14679
14680     /* Piece placement data */
14681     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14682         j = 0;
14683         for (;;) {
14684             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14685                 if (*p == '/') p++;
14686                 emptycount = gameInfo.boardWidth - j;
14687                 while (emptycount--)
14688                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14689                 break;
14690 #if(BOARD_FILES >= 10)
14691             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14692                 p++; emptycount=10;
14693                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14694                 while (emptycount--)
14695                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14696 #endif
14697             } else if (isdigit(*p)) {
14698                 emptycount = *p++ - '0';
14699                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14700                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14701                 while (emptycount--)
14702                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14703             } else if (*p == '+' || isalpha(*p)) {
14704                 if (j >= gameInfo.boardWidth) return FALSE;
14705                 if(*p=='+') {
14706                     piece = CharToPiece(*++p);
14707                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14708                     piece = (ChessSquare) (PROMOTED piece ); p++;
14709                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14710                 } else piece = CharToPiece(*p++);
14711
14712                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14713                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14714                     piece = (ChessSquare) (PROMOTED piece);
14715                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14716                     p++;
14717                 }
14718                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14719             } else {
14720                 return FALSE;
14721             }
14722         }
14723     }
14724     while (*p == '/' || *p == ' ') p++;
14725
14726     /* [HGM] look for Crazyhouse holdings here */
14727     while(*p==' ') p++;
14728     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14729         if(*p == '[') p++;
14730         if(*p == '-' ) *p++; /* empty holdings */ else {
14731             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14732             /* if we would allow FEN reading to set board size, we would   */
14733             /* have to add holdings and shift the board read so far here   */
14734             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14735                 *p++;
14736                 if((int) piece >= (int) BlackPawn ) {
14737                     i = (int)piece - (int)BlackPawn;
14738                     i = PieceToNumber((ChessSquare)i);
14739                     if( i >= gameInfo.holdingsSize ) return FALSE;
14740                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14741                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14742                 } else {
14743                     i = (int)piece - (int)WhitePawn;
14744                     i = PieceToNumber((ChessSquare)i);
14745                     if( i >= gameInfo.holdingsSize ) return FALSE;
14746                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14747                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14748                 }
14749             }
14750         }
14751         if(*p == ']') *p++;
14752     }
14753
14754     while(*p == ' ') p++;
14755
14756     /* Active color */
14757     switch (*p++) {
14758       case 'w':
14759         *blackPlaysFirst = FALSE;
14760         break;
14761       case 'b': 
14762         *blackPlaysFirst = TRUE;
14763         break;
14764       default:
14765         return FALSE;
14766     }
14767
14768     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14769     /* return the extra info in global variiables             */
14770
14771     /* set defaults in case FEN is incomplete */
14772     board[EP_STATUS] = EP_UNKNOWN;
14773     for(i=0; i<nrCastlingRights; i++ ) {
14774         board[CASTLING][i] =
14775             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14776     }   /* assume possible unless obviously impossible */
14777     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14778     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14779     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14780                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14781     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14782     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14783     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14784                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14785     FENrulePlies = 0;
14786
14787     while(*p==' ') p++;
14788     if(nrCastlingRights) {
14789       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14790           /* castling indicator present, so default becomes no castlings */
14791           for(i=0; i<nrCastlingRights; i++ ) {
14792                  board[CASTLING][i] = NoRights;
14793           }
14794       }
14795       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14796              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14797              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14798              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14799         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14800
14801         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14802             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14803             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14804         }
14805         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14806             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14807         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14808                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14809         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14810                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14811         switch(c) {
14812           case'K':
14813               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14814               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14815               board[CASTLING][2] = whiteKingFile;
14816               break;
14817           case'Q':
14818               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14819               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14820               board[CASTLING][2] = whiteKingFile;
14821               break;
14822           case'k':
14823               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14824               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14825               board[CASTLING][5] = blackKingFile;
14826               break;
14827           case'q':
14828               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14829               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14830               board[CASTLING][5] = blackKingFile;
14831           case '-':
14832               break;
14833           default: /* FRC castlings */
14834               if(c >= 'a') { /* black rights */
14835                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14836                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14837                   if(i == BOARD_RGHT) break;
14838                   board[CASTLING][5] = i;
14839                   c -= AAA;
14840                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14841                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14842                   if(c > i)
14843                       board[CASTLING][3] = c;
14844                   else
14845                       board[CASTLING][4] = c;
14846               } else { /* white rights */
14847                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14848                     if(board[0][i] == WhiteKing) break;
14849                   if(i == BOARD_RGHT) break;
14850                   board[CASTLING][2] = i;
14851                   c -= AAA - 'a' + 'A';
14852                   if(board[0][c] >= WhiteKing) break;
14853                   if(c > i)
14854                       board[CASTLING][0] = c;
14855                   else
14856                       board[CASTLING][1] = c;
14857               }
14858         }
14859       }
14860       for(i=0; i<nrCastlingRights; i++)
14861         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14862     if (appData.debugMode) {
14863         fprintf(debugFP, "FEN castling rights:");
14864         for(i=0; i<nrCastlingRights; i++)
14865         fprintf(debugFP, " %d", board[CASTLING][i]);
14866         fprintf(debugFP, "\n");
14867     }
14868
14869       while(*p==' ') p++;
14870     }
14871
14872     /* read e.p. field in games that know e.p. capture */
14873     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14874        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14875       if(*p=='-') {
14876         p++; board[EP_STATUS] = EP_NONE;
14877       } else {
14878          char c = *p++ - AAA;
14879
14880          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14881          if(*p >= '0' && *p <='9') *p++;
14882          board[EP_STATUS] = c;
14883       }
14884     }
14885
14886
14887     if(sscanf(p, "%d", &i) == 1) {
14888         FENrulePlies = i; /* 50-move ply counter */
14889         /* (The move number is still ignored)    */
14890     }
14891
14892     return TRUE;
14893 }
14894       
14895 void
14896 EditPositionPasteFEN(char *fen)
14897 {
14898   if (fen != NULL) {
14899     Board initial_position;
14900
14901     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14902       DisplayError(_("Bad FEN position in clipboard"), 0);
14903       return ;
14904     } else {
14905       int savedBlackPlaysFirst = blackPlaysFirst;
14906       EditPositionEvent();
14907       blackPlaysFirst = savedBlackPlaysFirst;
14908       CopyBoard(boards[0], initial_position);
14909       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14910       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14911       DisplayBothClocks();
14912       DrawPosition(FALSE, boards[currentMove]);
14913     }
14914   }
14915 }
14916
14917 static char cseq[12] = "\\   ";
14918
14919 Boolean set_cont_sequence(char *new_seq)
14920 {
14921     int len;
14922     Boolean ret;
14923
14924     // handle bad attempts to set the sequence
14925         if (!new_seq)
14926                 return 0; // acceptable error - no debug
14927
14928     len = strlen(new_seq);
14929     ret = (len > 0) && (len < sizeof(cseq));
14930     if (ret)
14931         strcpy(cseq, new_seq);
14932     else if (appData.debugMode)
14933         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14934     return ret;
14935 }
14936
14937 /*
14938     reformat a source message so words don't cross the width boundary.  internal
14939     newlines are not removed.  returns the wrapped size (no null character unless
14940     included in source message).  If dest is NULL, only calculate the size required
14941     for the dest buffer.  lp argument indicats line position upon entry, and it's
14942     passed back upon exit.
14943 */
14944 int wrap(char *dest, char *src, int count, int width, int *lp)
14945 {
14946     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14947
14948     cseq_len = strlen(cseq);
14949     old_line = line = *lp;
14950     ansi = len = clen = 0;
14951
14952     for (i=0; i < count; i++)
14953     {
14954         if (src[i] == '\033')
14955             ansi = 1;
14956
14957         // if we hit the width, back up
14958         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14959         {
14960             // store i & len in case the word is too long
14961             old_i = i, old_len = len;
14962
14963             // find the end of the last word
14964             while (i && src[i] != ' ' && src[i] != '\n')
14965             {
14966                 i--;
14967                 len--;
14968             }
14969
14970             // word too long?  restore i & len before splitting it
14971             if ((old_i-i+clen) >= width)
14972             {
14973                 i = old_i;
14974                 len = old_len;
14975             }
14976
14977             // extra space?
14978             if (i && src[i-1] == ' ')
14979                 len--;
14980
14981             if (src[i] != ' ' && src[i] != '\n')
14982             {
14983                 i--;
14984                 if (len)
14985                     len--;
14986             }
14987
14988             // now append the newline and continuation sequence
14989             if (dest)
14990                 dest[len] = '\n';
14991             len++;
14992             if (dest)
14993                 strncpy(dest+len, cseq, cseq_len);
14994             len += cseq_len;
14995             line = cseq_len;
14996             clen = cseq_len;
14997             continue;
14998         }
14999
15000         if (dest)
15001             dest[len] = src[i];
15002         len++;
15003         if (!ansi)
15004             line++;
15005         if (src[i] == '\n')
15006             line = 0;
15007         if (src[i] == 'm')
15008             ansi = 0;
15009     }
15010     if (dest && appData.debugMode)
15011     {
15012         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15013             count, width, line, len, *lp);
15014         show_bytes(debugFP, src, count);
15015         fprintf(debugFP, "\ndest: ");
15016         show_bytes(debugFP, dest, len);
15017         fprintf(debugFP, "\n");
15018     }
15019     *lp = dest ? line : old_line;
15020
15021     return len;
15022 }
15023
15024 // [HGM] vari: routines for shelving variations
15025
15026 void 
15027 PushTail(int firstMove, int lastMove)
15028 {
15029         int i, j, nrMoves = lastMove - firstMove;
15030
15031         if(appData.icsActive) { // only in local mode
15032                 forwardMostMove = currentMove; // mimic old ICS behavior
15033                 return;
15034         }
15035         if(storedGames >= MAX_VARIATIONS-1) return;
15036
15037         // push current tail of game on stack
15038         savedResult[storedGames] = gameInfo.result;
15039         savedDetails[storedGames] = gameInfo.resultDetails;
15040         gameInfo.resultDetails = NULL;
15041         savedFirst[storedGames] = firstMove;
15042         savedLast [storedGames] = lastMove;
15043         savedFramePtr[storedGames] = framePtr;
15044         framePtr -= nrMoves; // reserve space for the boards
15045         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15046             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15047             for(j=0; j<MOVE_LEN; j++)
15048                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15049             for(j=0; j<2*MOVE_LEN; j++)
15050                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15051             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15052             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15053             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15054             pvInfoList[firstMove+i-1].depth = 0;
15055             commentList[framePtr+i] = commentList[firstMove+i];
15056             commentList[firstMove+i] = NULL;
15057         }
15058
15059         storedGames++;
15060         forwardMostMove = firstMove; // truncate game so we can start variation
15061         if(storedGames == 1) GreyRevert(FALSE);
15062 }
15063
15064 Boolean
15065 PopTail(Boolean annotate)
15066 {
15067         int i, j, nrMoves;
15068         char buf[8000], moveBuf[20];
15069
15070         if(appData.icsActive) return FALSE; // only in local mode
15071         if(!storedGames) return FALSE; // sanity
15072         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15073
15074         storedGames--;
15075         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15076         nrMoves = savedLast[storedGames] - currentMove;
15077         if(annotate) {
15078                 int cnt = 10;
15079                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15080                 else strcpy(buf, "(");
15081                 for(i=currentMove; i<forwardMostMove; i++) {
15082                         if(WhiteOnMove(i))
15083                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15084                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15085                         strcat(buf, moveBuf);
15086                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15087                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15088                 }
15089                 strcat(buf, ")");
15090         }
15091         for(i=1; i<=nrMoves; i++) { // copy last variation back
15092             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15093             for(j=0; j<MOVE_LEN; j++)
15094                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15095             for(j=0; j<2*MOVE_LEN; j++)
15096                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15097             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15098             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15099             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15100             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15101             commentList[currentMove+i] = commentList[framePtr+i];
15102             commentList[framePtr+i] = NULL;
15103         }
15104         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15105         framePtr = savedFramePtr[storedGames];
15106         gameInfo.result = savedResult[storedGames];
15107         if(gameInfo.resultDetails != NULL) {
15108             free(gameInfo.resultDetails);
15109       }
15110         gameInfo.resultDetails = savedDetails[storedGames];
15111         forwardMostMove = currentMove + nrMoves;
15112         if(storedGames == 0) GreyRevert(TRUE);
15113         return TRUE;
15114 }
15115
15116 void 
15117 CleanupTail()
15118 {       // remove all shelved variations
15119         int i;
15120         for(i=0; i<storedGames; i++) {
15121             if(savedDetails[i])
15122                 free(savedDetails[i]);
15123             savedDetails[i] = NULL;
15124         }
15125         for(i=framePtr; i<MAX_MOVES; i++) {
15126                 if(commentList[i]) free(commentList[i]);
15127                 commentList[i] = NULL;
15128         }
15129         framePtr = MAX_MOVES-1;
15130         storedGames = 0;
15131 }
15132
15133 void
15134 LoadVariation(int index, char *text)
15135 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15136         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15137         int level = 0, move;
15138
15139         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15140         // first find outermost bracketing variation
15141         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15142             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15143                 if(*p == '{') wait = '}'; else
15144                 if(*p == '[') wait = ']'; else
15145                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15146                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15147             }
15148             if(*p == wait) wait = NULLCHAR; // closing ]} found
15149             p++;
15150         }
15151         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15152         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15153         end[1] = NULLCHAR; // clip off comment beyond variation
15154         ToNrEvent(currentMove-1);
15155         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15156         // kludge: use ParsePV() to append variation to game
15157         move = currentMove;
15158         ParsePV(start, TRUE);
15159         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15160         ClearPremoveHighlights();
15161         CommentPopDown();
15162         ToNrEvent(currentMove+1);
15163 }