new developer release
[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                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
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 char yy_textstr[8000];
4719
4720 /* Parser for moves from gnuchess, ICS, or user typein box */
4721 Boolean
4722 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4723      char *move;
4724      int moveNum;
4725      ChessMove *moveType;
4726      int *fromX, *fromY, *toX, *toY;
4727      char *promoChar;
4728 {       
4729     if (appData.debugMode) {
4730         fprintf(debugFP, "move to parse: %s\n", move);
4731     }
4732     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4733
4734     switch (*moveType) {
4735       case WhitePromotionChancellor:
4736       case BlackPromotionChancellor:
4737       case WhitePromotionArchbishop:
4738       case BlackPromotionArchbishop:
4739       case WhitePromotionQueen:
4740       case BlackPromotionQueen:
4741       case WhitePromotionRook:
4742       case BlackPromotionRook:
4743       case WhitePromotionBishop:
4744       case BlackPromotionBishop:
4745       case WhitePromotionKnight:
4746       case BlackPromotionKnight:
4747       case WhitePromotionKing:
4748       case BlackPromotionKing:
4749       case NormalMove:
4750       case WhiteCapturesEnPassant:
4751       case BlackCapturesEnPassant:
4752       case WhiteKingSideCastle:
4753       case WhiteQueenSideCastle:
4754       case BlackKingSideCastle:
4755       case BlackQueenSideCastle:
4756       case WhiteKingSideCastleWild:
4757       case WhiteQueenSideCastleWild:
4758       case BlackKingSideCastleWild:
4759       case BlackQueenSideCastleWild:
4760       /* Code added by Tord: */
4761       case WhiteHSideCastleFR:
4762       case WhiteASideCastleFR:
4763       case BlackHSideCastleFR:
4764       case BlackASideCastleFR:
4765       /* End of code added by Tord */
4766       case IllegalMove:         /* bug or odd chess variant */
4767         *fromX = currentMoveString[0] - AAA;
4768         *fromY = currentMoveString[1] - ONE;
4769         *toX = currentMoveString[2] - AAA;
4770         *toY = currentMoveString[3] - ONE;
4771         *promoChar = currentMoveString[4];
4772         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4773             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4774     if (appData.debugMode) {
4775         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4776     }
4777             *fromX = *fromY = *toX = *toY = 0;
4778             return FALSE;
4779         }
4780         if (appData.testLegality) {
4781           return (*moveType != IllegalMove);
4782         } else {
4783           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4784                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4785         }
4786
4787       case WhiteDrop:
4788       case BlackDrop:
4789         *fromX = *moveType == WhiteDrop ?
4790           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4791           (int) CharToPiece(ToLower(currentMoveString[0]));
4792         *fromY = DROP_RANK;
4793         *toX = currentMoveString[2] - AAA;
4794         *toY = currentMoveString[3] - ONE;
4795         *promoChar = NULLCHAR;
4796         return TRUE;
4797
4798       case AmbiguousMove:
4799       case ImpossibleMove:
4800       case (ChessMove) 0:       /* end of file */
4801       case ElapsedTime:
4802       case Comment:
4803       case PGNTag:
4804       case NAG:
4805       case WhiteWins:
4806       case BlackWins:
4807       case GameIsDrawn:
4808       default:
4809     if (appData.debugMode) {
4810         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4811     }
4812         /* bug? */
4813         *fromX = *fromY = *toX = *toY = 0;
4814         *promoChar = NULLCHAR;
4815         return FALSE;
4816     }
4817 }
4818
4819
4820 void
4821 ParsePV(char *pv, Boolean storeComments)
4822 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4823   int fromX, fromY, toX, toY; char promoChar;
4824   ChessMove moveType;
4825   Boolean valid;
4826   int nr = 0;
4827
4828   endPV = forwardMostMove;
4829   do {
4830     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4831     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4832     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4833 if(appData.debugMode){
4834 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4835 }
4836     if(!valid && nr == 0 &&
4837        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4838         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4839         // Hande case where played move is different from leading PV move
4840         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4841         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4842         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4843         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4844           endPV += 2; // if position different, keep this
4845           moveList[endPV-1][0] = fromX + AAA;
4846           moveList[endPV-1][1] = fromY + ONE;
4847           moveList[endPV-1][2] = toX + AAA;
4848           moveList[endPV-1][3] = toY + ONE;
4849           parseList[endPV-1][0] = NULLCHAR;
4850           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4851         }
4852       }
4853     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4854     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4855     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4856     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4857         valid++; // allow comments in PV
4858         continue;
4859     }
4860     nr++;
4861     if(endPV+1 > framePtr) break; // no space, truncate
4862     if(!valid) break;
4863     endPV++;
4864     CopyBoard(boards[endPV], boards[endPV-1]);
4865     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4866     moveList[endPV-1][0] = fromX + AAA;
4867     moveList[endPV-1][1] = fromY + ONE;
4868     moveList[endPV-1][2] = toX + AAA;
4869     moveList[endPV-1][3] = toY + ONE;
4870     if(storeComments)
4871         CoordsToAlgebraic(boards[endPV - 1],
4872                              PosFlags(endPV - 1),
4873                              fromY, fromX, toY, toX, promoChar,
4874                              parseList[endPV - 1]);
4875     else
4876         parseList[endPV-1][0] = NULLCHAR;
4877   } while(valid);
4878   currentMove = endPV;
4879   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4880   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4881                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4882   DrawPosition(TRUE, boards[currentMove]);
4883 }
4884
4885 static int lastX, lastY;
4886
4887 Boolean
4888 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4889 {
4890         int startPV;
4891         char *p;
4892
4893         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4894         lastX = x; lastY = y;
4895         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4896         startPV = index;
4897         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4898         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
4899         index = startPV;
4900         do{ while(buf[index] && buf[index] != '\n') index++;
4901         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
4902         buf[index] = 0;
4903         ParsePV(buf+startPV, FALSE);
4904         *start = startPV; *end = index-1;
4905         return TRUE;
4906 }
4907
4908 Boolean
4909 LoadPV(int x, int y)
4910 { // called on right mouse click to load PV
4911   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4912   lastX = x; lastY = y;
4913   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
4914   return TRUE;
4915 }
4916
4917 void
4918 UnLoadPV()
4919 {
4920   if(endPV < 0) return;
4921   endPV = -1;
4922   currentMove = forwardMostMove;
4923   ClearPremoveHighlights();
4924   DrawPosition(TRUE, boards[currentMove]);
4925 }
4926
4927 void
4928 MovePV(int x, int y, int h)
4929 { // step through PV based on mouse coordinates (called on mouse move)
4930   int margin = h>>3, step = 0;
4931
4932   if(endPV < 0) return;
4933   // we must somehow check if right button is still down (might be released off board!)
4934   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4935   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4936   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4937   if(!step) return;
4938   lastX = x; lastY = y;
4939   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4940   currentMove += step;
4941   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4942   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4943                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4944   DrawPosition(FALSE, boards[currentMove]);
4945 }
4946
4947
4948 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4949 // All positions will have equal probability, but the current method will not provide a unique
4950 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4951 #define DARK 1
4952 #define LITE 2
4953 #define ANY 3
4954
4955 int squaresLeft[4];
4956 int piecesLeft[(int)BlackPawn];
4957 int seed, nrOfShuffles;
4958
4959 void GetPositionNumber()
4960 {       // sets global variable seed
4961         int i;
4962
4963         seed = appData.defaultFrcPosition;
4964         if(seed < 0) { // randomize based on time for negative FRC position numbers
4965                 for(i=0; i<50; i++) seed += random();
4966                 seed = random() ^ random() >> 8 ^ random() << 8;
4967                 if(seed<0) seed = -seed;
4968         }
4969 }
4970
4971 int put(Board board, int pieceType, int rank, int n, int shade)
4972 // put the piece on the (n-1)-th empty squares of the given shade
4973 {
4974         int i;
4975
4976         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4977                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4978                         board[rank][i] = (ChessSquare) pieceType;
4979                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4980                         squaresLeft[ANY]--;
4981                         piecesLeft[pieceType]--; 
4982                         return i;
4983                 }
4984         }
4985         return -1;
4986 }
4987
4988
4989 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4990 // calculate where the next piece goes, (any empty square), and put it there
4991 {
4992         int i;
4993
4994         i = seed % squaresLeft[shade];
4995         nrOfShuffles *= squaresLeft[shade];
4996         seed /= squaresLeft[shade];
4997         put(board, pieceType, rank, i, shade);
4998 }
4999
5000 void AddTwoPieces(Board board, int pieceType, int rank)
5001 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5002 {
5003         int i, n=squaresLeft[ANY], j=n-1, k;
5004
5005         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5006         i = seed % k;  // pick one
5007         nrOfShuffles *= k;
5008         seed /= k;
5009         while(i >= j) i -= j--;
5010         j = n - 1 - j; i += j;
5011         put(board, pieceType, rank, j, ANY);
5012         put(board, pieceType, rank, i, ANY);
5013 }
5014
5015 void SetUpShuffle(Board board, int number)
5016 {
5017         int i, p, first=1;
5018
5019         GetPositionNumber(); nrOfShuffles = 1;
5020
5021         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5022         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5023         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5024
5025         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5026
5027         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5028             p = (int) board[0][i];
5029             if(p < (int) BlackPawn) piecesLeft[p] ++;
5030             board[0][i] = EmptySquare;
5031         }
5032
5033         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5034             // shuffles restricted to allow normal castling put KRR first
5035             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5036                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5037             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5038                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5039             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5040                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5041             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5042                 put(board, WhiteRook, 0, 0, ANY);
5043             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5044         }
5045
5046         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5047             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5048             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5049                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5050                 while(piecesLeft[p] >= 2) {
5051                     AddOnePiece(board, p, 0, LITE);
5052                     AddOnePiece(board, p, 0, DARK);
5053                 }
5054                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5055             }
5056
5057         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5058             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5059             // but we leave King and Rooks for last, to possibly obey FRC restriction
5060             if(p == (int)WhiteRook) continue;
5061             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5062             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5063         }
5064
5065         // now everything is placed, except perhaps King (Unicorn) and Rooks
5066
5067         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5068             // Last King gets castling rights
5069             while(piecesLeft[(int)WhiteUnicorn]) {
5070                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5071                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5072             }
5073
5074             while(piecesLeft[(int)WhiteKing]) {
5075                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5076                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5077             }
5078
5079
5080         } else {
5081             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5082             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5083         }
5084
5085         // Only Rooks can be left; simply place them all
5086         while(piecesLeft[(int)WhiteRook]) {
5087                 i = put(board, WhiteRook, 0, 0, ANY);
5088                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5089                         if(first) {
5090                                 first=0;
5091                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5092                         }
5093                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5094                 }
5095         }
5096         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5097             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5098         }
5099
5100         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5101 }
5102
5103 int SetCharTable( char *table, const char * map )
5104 /* [HGM] moved here from winboard.c because of its general usefulness */
5105 /*       Basically a safe strcpy that uses the last character as King */
5106 {
5107     int result = FALSE; int NrPieces;
5108
5109     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5110                     && NrPieces >= 12 && !(NrPieces&1)) {
5111         int i; /* [HGM] Accept even length from 12 to 34 */
5112
5113         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5114         for( i=0; i<NrPieces/2-1; i++ ) {
5115             table[i] = map[i];
5116             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5117         }
5118         table[(int) WhiteKing]  = map[NrPieces/2-1];
5119         table[(int) BlackKing]  = map[NrPieces-1];
5120
5121         result = TRUE;
5122     }
5123
5124     return result;
5125 }
5126
5127 void Prelude(Board board)
5128 {       // [HGM] superchess: random selection of exo-pieces
5129         int i, j, k; ChessSquare p; 
5130         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5131
5132         GetPositionNumber(); // use FRC position number
5133
5134         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5135             SetCharTable(pieceToChar, appData.pieceToCharTable);
5136             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5137                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5138         }
5139
5140         j = seed%4;                 seed /= 4; 
5141         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5142         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5143         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5144         j = seed%3 + (seed%3 >= j); seed /= 3; 
5145         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5146         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5147         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5148         j = seed%3;                 seed /= 3; 
5149         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5150         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5151         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5152         j = seed%2 + (seed%2 >= j); seed /= 2; 
5153         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5157         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5158         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5159         put(board, exoPieces[0],    0, 0, ANY);
5160         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5161 }
5162
5163 void
5164 InitPosition(redraw)
5165      int redraw;
5166 {
5167     ChessSquare (* pieces)[BOARD_FILES];
5168     int i, j, pawnRow, overrule,
5169     oldx = gameInfo.boardWidth,
5170     oldy = gameInfo.boardHeight,
5171     oldh = gameInfo.holdingsWidth,
5172     oldv = gameInfo.variant;
5173
5174     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5175
5176     /* [AS] Initialize pv info list [HGM] and game status */
5177     {
5178         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5179             pvInfoList[i].depth = 0;
5180             boards[i][EP_STATUS] = EP_NONE;
5181             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5182         }
5183
5184         initialRulePlies = 0; /* 50-move counter start */
5185
5186         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5187         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5188     }
5189
5190     
5191     /* [HGM] logic here is completely changed. In stead of full positions */
5192     /* the initialized data only consist of the two backranks. The switch */
5193     /* selects which one we will use, which is than copied to the Board   */
5194     /* initialPosition, which for the rest is initialized by Pawns and    */
5195     /* empty squares. This initial position is then copied to boards[0],  */
5196     /* possibly after shuffling, so that it remains available.            */
5197
5198     gameInfo.holdingsWidth = 0; /* default board sizes */
5199     gameInfo.boardWidth    = 8;
5200     gameInfo.boardHeight   = 8;
5201     gameInfo.holdingsSize  = 0;
5202     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5203     for(i=0; i<BOARD_FILES-2; i++)
5204       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5205     initialPosition[EP_STATUS] = EP_NONE;
5206     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5207
5208     switch (gameInfo.variant) {
5209     case VariantFischeRandom:
5210       shuffleOpenings = TRUE;
5211     default:
5212       pieces = FIDEArray;
5213       break;
5214     case VariantShatranj:
5215       pieces = ShatranjArray;
5216       nrCastlingRights = 0;
5217       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5218       break;
5219     case VariantMakruk:
5220       pieces = makrukArray;
5221       nrCastlingRights = 0;
5222       startedFromSetupPosition = TRUE;
5223       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5224       break;
5225     case VariantTwoKings:
5226       pieces = twoKingsArray;
5227       break;
5228     case VariantCapaRandom:
5229       shuffleOpenings = TRUE;
5230     case VariantCapablanca:
5231       pieces = CapablancaArray;
5232       gameInfo.boardWidth = 10;
5233       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5234       break;
5235     case VariantGothic:
5236       pieces = GothicArray;
5237       gameInfo.boardWidth = 10;
5238       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5239       break;
5240     case VariantJanus:
5241       pieces = JanusArray;
5242       gameInfo.boardWidth = 10;
5243       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5244       nrCastlingRights = 6;
5245         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5246         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5247         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5248         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5249         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5250         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5251       break;
5252     case VariantFalcon:
5253       pieces = FalconArray;
5254       gameInfo.boardWidth = 10;
5255       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5256       break;
5257     case VariantXiangqi:
5258       pieces = XiangqiArray;
5259       gameInfo.boardWidth  = 9;
5260       gameInfo.boardHeight = 10;
5261       nrCastlingRights = 0;
5262       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5263       break;
5264     case VariantShogi:
5265       pieces = ShogiArray;
5266       gameInfo.boardWidth  = 9;
5267       gameInfo.boardHeight = 9;
5268       gameInfo.holdingsSize = 7;
5269       nrCastlingRights = 0;
5270       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5271       break;
5272     case VariantCourier:
5273       pieces = CourierArray;
5274       gameInfo.boardWidth  = 12;
5275       nrCastlingRights = 0;
5276       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5277       break;
5278     case VariantKnightmate:
5279       pieces = KnightmateArray;
5280       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5281       break;
5282     case VariantFairy:
5283       pieces = fairyArray;
5284       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5285       break;
5286     case VariantGreat:
5287       pieces = GreatArray;
5288       gameInfo.boardWidth = 10;
5289       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5290       gameInfo.holdingsSize = 8;
5291       break;
5292     case VariantSuper:
5293       pieces = FIDEArray;
5294       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5295       gameInfo.holdingsSize = 8;
5296       startedFromSetupPosition = TRUE;
5297       break;
5298     case VariantCrazyhouse:
5299     case VariantBughouse:
5300       pieces = FIDEArray;
5301       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5302       gameInfo.holdingsSize = 5;
5303       break;
5304     case VariantWildCastle:
5305       pieces = FIDEArray;
5306       /* !!?shuffle with kings guaranteed to be on d or e file */
5307       shuffleOpenings = 1;
5308       break;
5309     case VariantNoCastle:
5310       pieces = FIDEArray;
5311       nrCastlingRights = 0;
5312       /* !!?unconstrained back-rank shuffle */
5313       shuffleOpenings = 1;
5314       break;
5315     }
5316
5317     overrule = 0;
5318     if(appData.NrFiles >= 0) {
5319         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5320         gameInfo.boardWidth = appData.NrFiles;
5321     }
5322     if(appData.NrRanks >= 0) {
5323         gameInfo.boardHeight = appData.NrRanks;
5324     }
5325     if(appData.holdingsSize >= 0) {
5326         i = appData.holdingsSize;
5327         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5328         gameInfo.holdingsSize = i;
5329     }
5330     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5331     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5332         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5333
5334     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5335     if(pawnRow < 1) pawnRow = 1;
5336     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5337
5338     /* User pieceToChar list overrules defaults */
5339     if(appData.pieceToCharTable != NULL)
5340         SetCharTable(pieceToChar, appData.pieceToCharTable);
5341
5342     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5343
5344         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5345             s = (ChessSquare) 0; /* account holding counts in guard band */
5346         for( i=0; i<BOARD_HEIGHT; i++ )
5347             initialPosition[i][j] = s;
5348
5349         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5350         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5351         initialPosition[pawnRow][j] = WhitePawn;
5352         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5353         if(gameInfo.variant == VariantXiangqi) {
5354             if(j&1) {
5355                 initialPosition[pawnRow][j] = 
5356                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5357                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5358                    initialPosition[2][j] = WhiteCannon;
5359                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5360                 }
5361             }
5362         }
5363         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5364     }
5365     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5366
5367             j=BOARD_LEFT+1;
5368             initialPosition[1][j] = WhiteBishop;
5369             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5370             j=BOARD_RGHT-2;
5371             initialPosition[1][j] = WhiteRook;
5372             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5373     }
5374
5375     if( nrCastlingRights == -1) {
5376         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5377         /*       This sets default castling rights from none to normal corners   */
5378         /* Variants with other castling rights must set them themselves above    */
5379         nrCastlingRights = 6;
5380        
5381         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5382         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5383         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5384         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5385         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5386         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5387      }
5388
5389      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5390      if(gameInfo.variant == VariantGreat) { // promotion commoners
5391         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5392         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5393         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5394         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5395      }
5396   if (appData.debugMode) {
5397     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5398   }
5399     if(shuffleOpenings) {
5400         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5401         startedFromSetupPosition = TRUE;
5402     }
5403     if(startedFromPositionFile) {
5404       /* [HGM] loadPos: use PositionFile for every new game */
5405       CopyBoard(initialPosition, filePosition);
5406       for(i=0; i<nrCastlingRights; i++)
5407           initialRights[i] = filePosition[CASTLING][i];
5408       startedFromSetupPosition = TRUE;
5409     }
5410
5411     CopyBoard(boards[0], initialPosition);
5412
5413     if(oldx != gameInfo.boardWidth ||
5414        oldy != gameInfo.boardHeight ||
5415        oldh != gameInfo.holdingsWidth
5416 #ifdef GOTHIC
5417        || oldv == VariantGothic ||        // For licensing popups
5418        gameInfo.variant == VariantGothic
5419 #endif
5420 #ifdef FALCON
5421        || oldv == VariantFalcon ||
5422        gameInfo.variant == VariantFalcon
5423 #endif
5424                                          )
5425             InitDrawingSizes(-2 ,0);
5426
5427     if (redraw)
5428       DrawPosition(TRUE, boards[currentMove]);
5429 }
5430
5431 void
5432 SendBoard(cps, moveNum)
5433      ChessProgramState *cps;
5434      int moveNum;
5435 {
5436     char message[MSG_SIZ];
5437     
5438     if (cps->useSetboard) {
5439       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5440       sprintf(message, "setboard %s\n", fen);
5441       SendToProgram(message, cps);
5442       free(fen);
5443
5444     } else {
5445       ChessSquare *bp;
5446       int i, j;
5447       /* Kludge to set black to move, avoiding the troublesome and now
5448        * deprecated "black" command.
5449        */
5450       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5451
5452       SendToProgram("edit\n", cps);
5453       SendToProgram("#\n", cps);
5454       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5455         bp = &boards[moveNum][i][BOARD_LEFT];
5456         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5457           if ((int) *bp < (int) BlackPawn) {
5458             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5459                     AAA + j, ONE + i);
5460             if(message[0] == '+' || message[0] == '~') {
5461                 sprintf(message, "%c%c%c+\n",
5462                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5463                         AAA + j, ONE + i);
5464             }
5465             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5466                 message[1] = BOARD_RGHT   - 1 - j + '1';
5467                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5468             }
5469             SendToProgram(message, cps);
5470           }
5471         }
5472       }
5473     
5474       SendToProgram("c\n", cps);
5475       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5476         bp = &boards[moveNum][i][BOARD_LEFT];
5477         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5478           if (((int) *bp != (int) EmptySquare)
5479               && ((int) *bp >= (int) BlackPawn)) {
5480             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5481                     AAA + j, ONE + i);
5482             if(message[0] == '+' || message[0] == '~') {
5483                 sprintf(message, "%c%c%c+\n",
5484                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5485                         AAA + j, ONE + i);
5486             }
5487             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5488                 message[1] = BOARD_RGHT   - 1 - j + '1';
5489                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5490             }
5491             SendToProgram(message, cps);
5492           }
5493         }
5494       }
5495     
5496       SendToProgram(".\n", cps);
5497     }
5498     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5499 }
5500
5501 static int autoQueen; // [HGM] oneclick
5502
5503 int
5504 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5505 {
5506     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5507     /* [HGM] add Shogi promotions */
5508     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5509     ChessSquare piece;
5510     ChessMove moveType;
5511     Boolean premove;
5512
5513     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5514     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5515
5516     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5517       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5518         return FALSE;
5519
5520     piece = boards[currentMove][fromY][fromX];
5521     if(gameInfo.variant == VariantShogi) {
5522         promotionZoneSize = 3;
5523         highestPromotingPiece = (int)WhiteFerz;
5524     } else if(gameInfo.variant == VariantMakruk) {
5525         promotionZoneSize = 3;
5526     }
5527
5528     // next weed out all moves that do not touch the promotion zone at all
5529     if((int)piece >= BlackPawn) {
5530         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5531              return FALSE;
5532         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5533     } else {
5534         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5535            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5536     }
5537
5538     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5539
5540     // weed out mandatory Shogi promotions
5541     if(gameInfo.variant == VariantShogi) {
5542         if(piece >= BlackPawn) {
5543             if(toY == 0 && piece == BlackPawn ||
5544                toY == 0 && piece == BlackQueen ||
5545                toY <= 1 && piece == BlackKnight) {
5546                 *promoChoice = '+';
5547                 return FALSE;
5548             }
5549         } else {
5550             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5551                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5552                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5553                 *promoChoice = '+';
5554                 return FALSE;
5555             }
5556         }
5557     }
5558
5559     // weed out obviously illegal Pawn moves
5560     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5561         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5562         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5563         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5564         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5565         // note we are not allowed to test for valid (non-)capture, due to premove
5566     }
5567
5568     // we either have a choice what to promote to, or (in Shogi) whether to promote
5569     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5570         *promoChoice = PieceToChar(BlackFerz);  // no choice
5571         return FALSE;
5572     }
5573     if(autoQueen) { // predetermined
5574         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5575              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5576         else *promoChoice = PieceToChar(BlackQueen);
5577         return FALSE;
5578     }
5579
5580     // suppress promotion popup on illegal moves that are not premoves
5581     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5582               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5583     if(appData.testLegality && !premove) {
5584         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5585                         fromY, fromX, toY, toX, NULLCHAR);
5586         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5587            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5588             return FALSE;
5589     }
5590
5591     return TRUE;
5592 }
5593
5594 int
5595 InPalace(row, column)
5596      int row, column;
5597 {   /* [HGM] for Xiangqi */
5598     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5599          column < (BOARD_WIDTH + 4)/2 &&
5600          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5601     return FALSE;
5602 }
5603
5604 int
5605 PieceForSquare (x, y)
5606      int x;
5607      int y;
5608 {
5609   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5610      return -1;
5611   else
5612      return boards[currentMove][y][x];
5613 }
5614
5615 int
5616 OKToStartUserMove(x, y)
5617      int x, y;
5618 {
5619     ChessSquare from_piece;
5620     int white_piece;
5621
5622     if (matchMode) return FALSE;
5623     if (gameMode == EditPosition) return TRUE;
5624
5625     if (x >= 0 && y >= 0)
5626       from_piece = boards[currentMove][y][x];
5627     else
5628       from_piece = EmptySquare;
5629
5630     if (from_piece == EmptySquare) return FALSE;
5631
5632     white_piece = (int)from_piece >= (int)WhitePawn &&
5633       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5634
5635     switch (gameMode) {
5636       case PlayFromGameFile:
5637       case AnalyzeFile:
5638       case TwoMachinesPlay:
5639       case EndOfGame:
5640         return FALSE;
5641
5642       case IcsObserving:
5643       case IcsIdle:
5644         return FALSE;
5645
5646       case MachinePlaysWhite:
5647       case IcsPlayingBlack:
5648         if (appData.zippyPlay) return FALSE;
5649         if (white_piece) {
5650             DisplayMoveError(_("You are playing Black"));
5651             return FALSE;
5652         }
5653         break;
5654
5655       case MachinePlaysBlack:
5656       case IcsPlayingWhite:
5657         if (appData.zippyPlay) return FALSE;
5658         if (!white_piece) {
5659             DisplayMoveError(_("You are playing White"));
5660             return FALSE;
5661         }
5662         break;
5663
5664       case EditGame:
5665         if (!white_piece && WhiteOnMove(currentMove)) {
5666             DisplayMoveError(_("It is White's turn"));
5667             return FALSE;
5668         }           
5669         if (white_piece && !WhiteOnMove(currentMove)) {
5670             DisplayMoveError(_("It is Black's turn"));
5671             return FALSE;
5672         }           
5673         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5674             /* Editing correspondence game history */
5675             /* Could disallow this or prompt for confirmation */
5676             cmailOldMove = -1;
5677         }
5678         break;
5679
5680       case BeginningOfGame:
5681         if (appData.icsActive) return FALSE;
5682         if (!appData.noChessProgram) {
5683             if (!white_piece) {
5684                 DisplayMoveError(_("You are playing White"));
5685                 return FALSE;
5686             }
5687         }
5688         break;
5689         
5690       case Training:
5691         if (!white_piece && WhiteOnMove(currentMove)) {
5692             DisplayMoveError(_("It is White's turn"));
5693             return FALSE;
5694         }           
5695         if (white_piece && !WhiteOnMove(currentMove)) {
5696             DisplayMoveError(_("It is Black's turn"));
5697             return FALSE;
5698         }           
5699         break;
5700
5701       default:
5702       case IcsExamining:
5703         break;
5704     }
5705     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5706         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5707         && gameMode != AnalyzeFile && gameMode != Training) {
5708         DisplayMoveError(_("Displayed position is not current"));
5709         return FALSE;
5710     }
5711     return TRUE;
5712 }
5713
5714 Boolean
5715 OnlyMove(int *x, int *y, Boolean captures) {
5716     DisambiguateClosure cl;
5717     if (appData.zippyPlay) return FALSE;
5718     switch(gameMode) {
5719       case MachinePlaysBlack:
5720       case IcsPlayingWhite:
5721       case BeginningOfGame:
5722         if(!WhiteOnMove(currentMove)) return FALSE;
5723         break;
5724       case MachinePlaysWhite:
5725       case IcsPlayingBlack:
5726         if(WhiteOnMove(currentMove)) return FALSE;
5727         break;
5728       default:
5729         return FALSE;
5730     }
5731     cl.pieceIn = EmptySquare; 
5732     cl.rfIn = *y;
5733     cl.ffIn = *x;
5734     cl.rtIn = -1;
5735     cl.ftIn = -1;
5736     cl.promoCharIn = NULLCHAR;
5737     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5738     if( cl.kind == NormalMove ||
5739         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5740         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5741         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5742         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5743       fromX = cl.ff;
5744       fromY = cl.rf;
5745       *x = cl.ft;
5746       *y = cl.rt;
5747       return TRUE;
5748     }
5749     if(cl.kind != ImpossibleMove) return FALSE;
5750     cl.pieceIn = EmptySquare;
5751     cl.rfIn = -1;
5752     cl.ffIn = -1;
5753     cl.rtIn = *y;
5754     cl.ftIn = *x;
5755     cl.promoCharIn = NULLCHAR;
5756     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5757     if( cl.kind == NormalMove ||
5758         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5759         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5760         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5761         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5762       fromX = cl.ff;
5763       fromY = cl.rf;
5764       *x = cl.ft;
5765       *y = cl.rt;
5766       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5767       return TRUE;
5768     }
5769     return FALSE;
5770 }
5771
5772 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5773 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5774 int lastLoadGameUseList = FALSE;
5775 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5776 ChessMove lastLoadGameStart = (ChessMove) 0;
5777
5778 ChessMove
5779 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5780      int fromX, fromY, toX, toY;
5781      int promoChar;
5782      Boolean captureOwn;
5783 {
5784     ChessMove moveType;
5785     ChessSquare pdown, pup;
5786
5787     /* Check if the user is playing in turn.  This is complicated because we
5788        let the user "pick up" a piece before it is his turn.  So the piece he
5789        tried to pick up may have been captured by the time he puts it down!
5790        Therefore we use the color the user is supposed to be playing in this
5791        test, not the color of the piece that is currently on the starting
5792        square---except in EditGame mode, where the user is playing both
5793        sides; fortunately there the capture race can't happen.  (It can
5794        now happen in IcsExamining mode, but that's just too bad.  The user
5795        will get a somewhat confusing message in that case.)
5796        */
5797
5798     switch (gameMode) {
5799       case PlayFromGameFile:
5800       case AnalyzeFile:
5801       case TwoMachinesPlay:
5802       case EndOfGame:
5803       case IcsObserving:
5804       case IcsIdle:
5805         /* We switched into a game mode where moves are not accepted,
5806            perhaps while the mouse button was down. */
5807         return ImpossibleMove;
5808
5809       case MachinePlaysWhite:
5810         /* User is moving for Black */
5811         if (WhiteOnMove(currentMove)) {
5812             DisplayMoveError(_("It is White's turn"));
5813             return ImpossibleMove;
5814         }
5815         break;
5816
5817       case MachinePlaysBlack:
5818         /* User is moving for White */
5819         if (!WhiteOnMove(currentMove)) {
5820             DisplayMoveError(_("It is Black's turn"));
5821             return ImpossibleMove;
5822         }
5823         break;
5824
5825       case EditGame:
5826       case IcsExamining:
5827       case BeginningOfGame:
5828       case AnalyzeMode:
5829       case Training:
5830         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5831             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5832             /* User is moving for Black */
5833             if (WhiteOnMove(currentMove)) {
5834                 DisplayMoveError(_("It is White's turn"));
5835                 return ImpossibleMove;
5836             }
5837         } else {
5838             /* User is moving for White */
5839             if (!WhiteOnMove(currentMove)) {
5840                 DisplayMoveError(_("It is Black's turn"));
5841                 return ImpossibleMove;
5842             }
5843         }
5844         break;
5845
5846       case IcsPlayingBlack:
5847         /* User is moving for Black */
5848         if (WhiteOnMove(currentMove)) {
5849             if (!appData.premove) {
5850                 DisplayMoveError(_("It is White's turn"));
5851             } else if (toX >= 0 && toY >= 0) {
5852                 premoveToX = toX;
5853                 premoveToY = toY;
5854                 premoveFromX = fromX;
5855                 premoveFromY = fromY;
5856                 premovePromoChar = promoChar;
5857                 gotPremove = 1;
5858                 if (appData.debugMode) 
5859                     fprintf(debugFP, "Got premove: fromX %d,"
5860                             "fromY %d, toX %d, toY %d\n",
5861                             fromX, fromY, toX, toY);
5862             }
5863             return ImpossibleMove;
5864         }
5865         break;
5866
5867       case IcsPlayingWhite:
5868         /* User is moving for White */
5869         if (!WhiteOnMove(currentMove)) {
5870             if (!appData.premove) {
5871                 DisplayMoveError(_("It is Black's turn"));
5872             } else if (toX >= 0 && toY >= 0) {
5873                 premoveToX = toX;
5874                 premoveToY = toY;
5875                 premoveFromX = fromX;
5876                 premoveFromY = fromY;
5877                 premovePromoChar = promoChar;
5878                 gotPremove = 1;
5879                 if (appData.debugMode) 
5880                     fprintf(debugFP, "Got premove: fromX %d,"
5881                             "fromY %d, toX %d, toY %d\n",
5882                             fromX, fromY, toX, toY);
5883             }
5884             return ImpossibleMove;
5885         }
5886         break;
5887
5888       default:
5889         break;
5890
5891       case EditPosition:
5892         /* EditPosition, empty square, or different color piece;
5893            click-click move is possible */
5894         if (toX == -2 || toY == -2) {
5895             boards[0][fromY][fromX] = EmptySquare;
5896             return AmbiguousMove;
5897         } else if (toX >= 0 && toY >= 0) {
5898             boards[0][toY][toX] = boards[0][fromY][fromX];
5899             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5900                 if(boards[0][fromY][0] != EmptySquare) {
5901                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5902                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5903                 }
5904             } else
5905             if(fromX == BOARD_RGHT+1) {
5906                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5907                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5908                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5909                 }
5910             } else
5911             boards[0][fromY][fromX] = EmptySquare;
5912             return AmbiguousMove;
5913         }
5914         return ImpossibleMove;
5915     }
5916
5917     if(toX < 0 || toY < 0) return ImpossibleMove;
5918     pdown = boards[currentMove][fromY][fromX];
5919     pup = boards[currentMove][toY][toX];
5920
5921     /* [HGM] If move started in holdings, it means a drop */
5922     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5923          if( pup != EmptySquare ) return ImpossibleMove;
5924          if(appData.testLegality) {
5925              /* it would be more logical if LegalityTest() also figured out
5926               * which drops are legal. For now we forbid pawns on back rank.
5927               * Shogi is on its own here...
5928               */
5929              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5930                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5931                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5932          }
5933          return WhiteDrop; /* Not needed to specify white or black yet */
5934     }
5935
5936     /* [HGM] always test for legality, to get promotion info */
5937     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5938                                          fromY, fromX, toY, toX, promoChar);
5939     /* [HGM] but possibly ignore an IllegalMove result */
5940     if (appData.testLegality) {
5941         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5942             DisplayMoveError(_("Illegal move"));
5943             return ImpossibleMove;
5944         }
5945     }
5946
5947     return moveType;
5948     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5949        function is made into one that returns an OK move type if FinishMove
5950        should be called. This to give the calling driver routine the
5951        opportunity to finish the userMove input with a promotion popup,
5952        without bothering the user with this for invalid or illegal moves */
5953
5954 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5955 }
5956
5957 /* Common tail of UserMoveEvent and DropMenuEvent */
5958 int
5959 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5960      ChessMove moveType;
5961      int fromX, fromY, toX, toY;
5962      /*char*/int promoChar;
5963 {
5964     char *bookHit = 0;
5965
5966     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5967         // [HGM] superchess: suppress promotions to non-available piece
5968         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5969         if(WhiteOnMove(currentMove)) {
5970             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5971         } else {
5972             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5973         }
5974     }
5975
5976     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5977        move type in caller when we know the move is a legal promotion */
5978     if(moveType == NormalMove && promoChar)
5979         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5980
5981     /* [HGM] convert drag-and-drop piece drops to standard form */
5982     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5983          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5984            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5985                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5986            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5987            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5988            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5989            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5990          fromY = DROP_RANK;
5991     }
5992
5993     /* [HGM] <popupFix> The following if has been moved here from
5994        UserMoveEvent(). Because it seemed to belong here (why not allow
5995        piece drops in training games?), and because it can only be
5996        performed after it is known to what we promote. */
5997     if (gameMode == Training) {
5998       /* compare the move played on the board to the next move in the
5999        * game. If they match, display the move and the opponent's response. 
6000        * If they don't match, display an error message.
6001        */
6002       int saveAnimate;
6003       Board testBoard;
6004       CopyBoard(testBoard, boards[currentMove]);
6005       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6006
6007       if (CompareBoards(testBoard, boards[currentMove+1])) {
6008         ForwardInner(currentMove+1);
6009
6010         /* Autoplay the opponent's response.
6011          * if appData.animate was TRUE when Training mode was entered,
6012          * the response will be animated.
6013          */
6014         saveAnimate = appData.animate;
6015         appData.animate = animateTraining;
6016         ForwardInner(currentMove+1);
6017         appData.animate = saveAnimate;
6018
6019         /* check for the end of the game */
6020         if (currentMove >= forwardMostMove) {
6021           gameMode = PlayFromGameFile;
6022           ModeHighlight();
6023           SetTrainingModeOff();
6024           DisplayInformation(_("End of game"));
6025         }
6026       } else {
6027         DisplayError(_("Incorrect move"), 0);
6028       }
6029       return 1;
6030     }
6031
6032   /* Ok, now we know that the move is good, so we can kill
6033      the previous line in Analysis Mode */
6034   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6035                                 && currentMove < forwardMostMove) {
6036     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6037   }
6038
6039   /* If we need the chess program but it's dead, restart it */
6040   ResurrectChessProgram();
6041
6042   /* A user move restarts a paused game*/
6043   if (pausing)
6044     PauseEvent();
6045
6046   thinkOutput[0] = NULLCHAR;
6047
6048   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6049
6050   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6051
6052   if (gameMode == BeginningOfGame) {
6053     if (appData.noChessProgram) {
6054       gameMode = EditGame;
6055       SetGameInfo();
6056     } else {
6057       char buf[MSG_SIZ];
6058       gameMode = MachinePlaysBlack;
6059       StartClocks();
6060       SetGameInfo();
6061       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6062       DisplayTitle(buf);
6063       if (first.sendName) {
6064         sprintf(buf, "name %s\n", gameInfo.white);
6065         SendToProgram(buf, &first);
6066       }
6067       StartClocks();
6068     }
6069     ModeHighlight();
6070   }
6071
6072   /* Relay move to ICS or chess engine */
6073   if (appData.icsActive) {
6074     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6075         gameMode == IcsExamining) {
6076       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6077         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6078         SendToICS("draw ");
6079         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6080       }
6081       // also send plain move, in case ICS does not understand atomic claims
6082       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6083       ics_user_moved = 1;
6084     }
6085   } else {
6086     if (first.sendTime && (gameMode == BeginningOfGame ||
6087                            gameMode == MachinePlaysWhite ||
6088                            gameMode == MachinePlaysBlack)) {
6089       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6090     }
6091     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6092          // [HGM] book: if program might be playing, let it use book
6093         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6094         first.maybeThinking = TRUE;
6095     } else SendMoveToProgram(forwardMostMove-1, &first);
6096     if (currentMove == cmailOldMove + 1) {
6097       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6098     }
6099   }
6100
6101   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6102
6103   switch (gameMode) {
6104   case EditGame:
6105     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6106     case MT_NONE:
6107     case MT_CHECK:
6108       break;
6109     case MT_CHECKMATE:
6110     case MT_STAINMATE:
6111       if (WhiteOnMove(currentMove)) {
6112         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6113       } else {
6114         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6115       }
6116       break;
6117     case MT_STALEMATE:
6118       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6119       break;
6120     }
6121     break;
6122     
6123   case MachinePlaysBlack:
6124   case MachinePlaysWhite:
6125     /* disable certain menu options while machine is thinking */
6126     SetMachineThinkingEnables();
6127     break;
6128
6129   default:
6130     break;
6131   }
6132
6133   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6134         
6135   if(bookHit) { // [HGM] book: simulate book reply
6136         static char bookMove[MSG_SIZ]; // a bit generous?
6137
6138         programStats.nodes = programStats.depth = programStats.time = 
6139         programStats.score = programStats.got_only_move = 0;
6140         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6141
6142         strcpy(bookMove, "move ");
6143         strcat(bookMove, bookHit);
6144         HandleMachineMove(bookMove, &first);
6145   }
6146   return 1;
6147 }
6148
6149 void
6150 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6151      int fromX, fromY, toX, toY;
6152      int promoChar;
6153 {
6154     /* [HGM] This routine was added to allow calling of its two logical
6155        parts from other modules in the old way. Before, UserMoveEvent()
6156        automatically called FinishMove() if the move was OK, and returned
6157        otherwise. I separated the two, in order to make it possible to
6158        slip a promotion popup in between. But that it always needs two
6159        calls, to the first part, (now called UserMoveTest() ), and to
6160        FinishMove if the first part succeeded. Calls that do not need
6161        to do anything in between, can call this routine the old way. 
6162     */
6163     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6164 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6165     if(moveType == AmbiguousMove)
6166         DrawPosition(FALSE, boards[currentMove]);
6167     else if(moveType != ImpossibleMove && moveType != Comment)
6168         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6169 }
6170
6171 void
6172 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6173      Board board;
6174      int flags;
6175      ChessMove kind;
6176      int rf, ff, rt, ft;
6177      VOIDSTAR closure;
6178 {
6179     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6180     Markers *m = (Markers *) closure;
6181     if(rf == fromY && ff == fromX)
6182         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6183                          || kind == WhiteCapturesEnPassant
6184                          || kind == BlackCapturesEnPassant);
6185     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6186 }
6187
6188 void
6189 MarkTargetSquares(int clear)
6190 {
6191   int x, y;
6192   if(!appData.markers || !appData.highlightDragging || 
6193      !appData.testLegality || gameMode == EditPosition) return;
6194   if(clear) {
6195     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6196   } else {
6197     int capt = 0;
6198     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6199     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6200       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6201       if(capt)
6202       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6203     }
6204   }
6205   DrawPosition(TRUE, NULL);
6206 }
6207
6208 void LeftClick(ClickType clickType, int xPix, int yPix)
6209 {
6210     int x, y;
6211     Boolean saveAnimate;
6212     static int second = 0, promotionChoice = 0;
6213     char promoChoice = NULLCHAR;
6214
6215     if(appData.seekGraph && appData.icsActive && loggedOn &&
6216         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6217         SeekGraphClick(clickType, xPix, yPix, 0);
6218         return;
6219     }
6220
6221     if (clickType == Press) ErrorPopDown();
6222     MarkTargetSquares(1);
6223
6224     x = EventToSquare(xPix, BOARD_WIDTH);
6225     y = EventToSquare(yPix, BOARD_HEIGHT);
6226     if (!flipView && y >= 0) {
6227         y = BOARD_HEIGHT - 1 - y;
6228     }
6229     if (flipView && x >= 0) {
6230         x = BOARD_WIDTH - 1 - x;
6231     }
6232
6233     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6234         if(clickType == Release) return; // ignore upclick of click-click destination
6235         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6236         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6237         if(gameInfo.holdingsWidth && 
6238                 (WhiteOnMove(currentMove) 
6239                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6240                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6241             // click in right holdings, for determining promotion piece
6242             ChessSquare p = boards[currentMove][y][x];
6243             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6244             if(p != EmptySquare) {
6245                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6246                 fromX = fromY = -1;
6247                 return;
6248             }
6249         }
6250         DrawPosition(FALSE, boards[currentMove]);
6251         return;
6252     }
6253
6254     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6255     if(clickType == Press
6256             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6257               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6258               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6259         return;
6260
6261     autoQueen = appData.alwaysPromoteToQueen;
6262
6263     if (fromX == -1) {
6264       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6265         if (clickType == Press) {
6266             /* First square */
6267             if (OKToStartUserMove(x, y)) {
6268                 fromX = x;
6269                 fromY = y;
6270                 second = 0;
6271                 MarkTargetSquares(0);
6272                 DragPieceBegin(xPix, yPix);
6273                 if (appData.highlightDragging) {
6274                     SetHighlights(x, y, -1, -1);
6275                 }
6276             }
6277         }
6278         return;
6279       }
6280     }
6281
6282     /* fromX != -1 */
6283     if (clickType == Press && gameMode != EditPosition) {
6284         ChessSquare fromP;
6285         ChessSquare toP;
6286         int frc;
6287
6288         // ignore off-board to clicks
6289         if(y < 0 || x < 0) return;
6290
6291         /* Check if clicking again on the same color piece */
6292         fromP = boards[currentMove][fromY][fromX];
6293         toP = boards[currentMove][y][x];
6294         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6295         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6296              WhitePawn <= toP && toP <= WhiteKing &&
6297              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6298              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6299             (BlackPawn <= fromP && fromP <= BlackKing && 
6300              BlackPawn <= toP && toP <= BlackKing &&
6301              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6302              !(fromP == BlackKing && toP == BlackRook && frc))) {
6303             /* Clicked again on same color piece -- changed his mind */
6304             second = (x == fromX && y == fromY);
6305            if(!second || !OnlyMove(&x, &y, TRUE)) {
6306             if (appData.highlightDragging) {
6307                 SetHighlights(x, y, -1, -1);
6308             } else {
6309                 ClearHighlights();
6310             }
6311             if (OKToStartUserMove(x, y)) {
6312                 fromX = x;
6313                 fromY = y;
6314                 MarkTargetSquares(0);
6315                 DragPieceBegin(xPix, yPix);
6316             }
6317             return;
6318            }
6319         }
6320         // ignore clicks on holdings
6321         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6322     }
6323
6324     if (clickType == Release && x == fromX && y == fromY) {
6325         DragPieceEnd(xPix, yPix);
6326         if (appData.animateDragging) {
6327             /* Undo animation damage if any */
6328             DrawPosition(FALSE, NULL);
6329         }
6330         if (second) {
6331             /* Second up/down in same square; just abort move */
6332             second = 0;
6333             fromX = fromY = -1;
6334             ClearHighlights();
6335             gotPremove = 0;
6336             ClearPremoveHighlights();
6337         } else {
6338             /* First upclick in same square; start click-click mode */
6339             SetHighlights(x, y, -1, -1);
6340         }
6341         return;
6342     }
6343
6344     /* we now have a different from- and (possibly off-board) to-square */
6345     /* Completed move */
6346     toX = x;
6347     toY = y;
6348     saveAnimate = appData.animate;
6349     if (clickType == Press) {
6350         /* Finish clickclick move */
6351         if (appData.animate || appData.highlightLastMove) {
6352             SetHighlights(fromX, fromY, toX, toY);
6353         } else {
6354             ClearHighlights();
6355         }
6356     } else {
6357         /* Finish drag move */
6358         if (appData.highlightLastMove) {
6359             SetHighlights(fromX, fromY, toX, toY);
6360         } else {
6361             ClearHighlights();
6362         }
6363         DragPieceEnd(xPix, yPix);
6364         /* Don't animate move and drag both */
6365         appData.animate = FALSE;
6366     }
6367
6368     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6369     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6370         ChessSquare piece = boards[currentMove][fromY][fromX];
6371         if(gameMode == EditPosition && piece != EmptySquare &&
6372            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6373             int n;
6374              
6375             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6376                 n = PieceToNumber(piece - (int)BlackPawn);
6377                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6378                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6379                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6380             } else
6381             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6382                 n = PieceToNumber(piece);
6383                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6384                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6385                 boards[currentMove][n][BOARD_WIDTH-2]++;
6386             }
6387             boards[currentMove][fromY][fromX] = EmptySquare;
6388         }
6389         ClearHighlights();
6390         fromX = fromY = -1;
6391         DrawPosition(TRUE, boards[currentMove]);
6392         return;
6393     }
6394
6395     // off-board moves should not be highlighted
6396     if(x < 0 || x < 0) ClearHighlights();
6397
6398     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6399         SetHighlights(fromX, fromY, toX, toY);
6400         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6401             // [HGM] super: promotion to captured piece selected from holdings
6402             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6403             promotionChoice = TRUE;
6404             // kludge follows to temporarily execute move on display, without promoting yet
6405             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6406             boards[currentMove][toY][toX] = p;
6407             DrawPosition(FALSE, boards[currentMove]);
6408             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6409             boards[currentMove][toY][toX] = q;
6410             DisplayMessage("Click in holdings to choose piece", "");
6411             return;
6412         }
6413         PromotionPopUp();
6414     } else {
6415         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6416         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6417         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6418         fromX = fromY = -1;
6419     }
6420     appData.animate = saveAnimate;
6421     if (appData.animate || appData.animateDragging) {
6422         /* Undo animation damage if needed */
6423         DrawPosition(FALSE, NULL);
6424     }
6425 }
6426
6427 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6428 {   // front-end-free part taken out of PieceMenuPopup
6429     int whichMenu; int xSqr, ySqr;
6430
6431     if(seekGraphUp) { // [HGM] seekgraph
6432         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6433         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6434         return -2;
6435     }
6436
6437     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6438          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6439         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6440         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6441         return -2;
6442     }
6443
6444     xSqr = EventToSquare(x, BOARD_WIDTH);
6445     ySqr = EventToSquare(y, BOARD_HEIGHT);
6446     if (action == Release) UnLoadPV(); // [HGM] pv
6447     if (action != Press) return -2; // return code to be ignored
6448     switch (gameMode) {
6449       case IcsExamining:
6450         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6451       case EditPosition:
6452         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6453         if (xSqr < 0 || ySqr < 0) return -1;\r
6454         whichMenu = 0; // edit-position menu
6455         break;
6456       case IcsObserving:
6457         if(!appData.icsEngineAnalyze) return -1;
6458       case IcsPlayingWhite:
6459       case IcsPlayingBlack:
6460         if(!appData.zippyPlay) goto noZip;
6461       case AnalyzeMode:
6462       case AnalyzeFile:
6463       case MachinePlaysWhite:
6464       case MachinePlaysBlack:
6465       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6466         if (!appData.dropMenu) {
6467           LoadPV(x, y);
6468           return 2; // flag front-end to grab mouse events
6469         }
6470         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6471            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6472       case EditGame:
6473       noZip:
6474         if (xSqr < 0 || ySqr < 0) return -1;
6475         if (!appData.dropMenu || appData.testLegality &&
6476             gameInfo.variant != VariantBughouse &&
6477             gameInfo.variant != VariantCrazyhouse) return -1;
6478         whichMenu = 1; // drop menu
6479         break;
6480       default:
6481         return -1;
6482     }
6483
6484     if (((*fromX = xSqr) < 0) ||
6485         ((*fromY = ySqr) < 0)) {
6486         *fromX = *fromY = -1;
6487         return -1;
6488     }
6489     if (flipView)
6490       *fromX = BOARD_WIDTH - 1 - *fromX;
6491     else
6492       *fromY = BOARD_HEIGHT - 1 - *fromY;
6493
6494     return whichMenu;
6495 }
6496
6497 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6498 {
6499 //    char * hint = lastHint;
6500     FrontEndProgramStats stats;
6501
6502     stats.which = cps == &first ? 0 : 1;
6503     stats.depth = cpstats->depth;
6504     stats.nodes = cpstats->nodes;
6505     stats.score = cpstats->score;
6506     stats.time = cpstats->time;
6507     stats.pv = cpstats->movelist;
6508     stats.hint = lastHint;
6509     stats.an_move_index = 0;
6510     stats.an_move_count = 0;
6511
6512     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6513         stats.hint = cpstats->move_name;
6514         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6515         stats.an_move_count = cpstats->nr_moves;
6516     }
6517
6518     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6519
6520     SetProgramStats( &stats );
6521 }
6522
6523 int
6524 Adjudicate(ChessProgramState *cps)
6525 {       // [HGM] some adjudications useful with buggy engines
6526         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6527         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6528         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6529         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6530         int k, count = 0; static int bare = 1;
6531         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6532         Boolean canAdjudicate = !appData.icsActive;
6533
6534         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6535         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6536             if( appData.testLegality )
6537             {   /* [HGM] Some more adjudications for obstinate engines */
6538                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6539                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6540                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6541                 static int moveCount = 6;
6542                 ChessMove result;
6543                 char *reason = NULL;
6544
6545                 /* Count what is on board. */
6546                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6547                 {   ChessSquare p = boards[forwardMostMove][i][j];
6548                     int m=i;
6549
6550                     switch((int) p)
6551                     {   /* count B,N,R and other of each side */
6552                         case WhiteKing:
6553                         case BlackKing:
6554                              NrK++; break; // [HGM] atomic: count Kings
6555                         case WhiteKnight:
6556                              NrWN++; break;
6557                         case WhiteBishop:
6558                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6559                              bishopsColor |= 1 << ((i^j)&1);
6560                              NrWB++; break;
6561                         case BlackKnight:
6562                              NrBN++; break;
6563                         case BlackBishop:
6564                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6565                              bishopsColor |= 1 << ((i^j)&1);
6566                              NrBB++; break;
6567                         case WhiteRook:
6568                              NrWR++; break;
6569                         case BlackRook:
6570                              NrBR++; break;
6571                         case WhiteQueen:
6572                              NrWQ++; break;
6573                         case BlackQueen:
6574                              NrBQ++; break;
6575                         case EmptySquare: 
6576                              break;
6577                         case BlackPawn:
6578                              m = 7-i;
6579                         case WhitePawn:
6580                              PawnAdvance += m; NrPawns++;
6581                     }
6582                     NrPieces += (p != EmptySquare);
6583                     NrW += ((int)p < (int)BlackPawn);
6584                     if(gameInfo.variant == VariantXiangqi && 
6585                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6586                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6587                         NrW -= ((int)p < (int)BlackPawn);
6588                     }
6589                 }
6590
6591                 /* Some material-based adjudications that have to be made before stalemate test */
6592                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6593                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6594                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6595                      if(canAdjudicate && appData.checkMates) {
6596                          if(engineOpponent)
6597                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6598                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6599                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6600                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6601                          return 1;
6602                      }
6603                 }
6604
6605                 /* Bare King in Shatranj (loses) or Losers (wins) */
6606                 if( NrW == 1 || NrPieces - NrW == 1) {
6607                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6608                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6609                      if(canAdjudicate && appData.checkMates) {
6610                          if(engineOpponent)
6611                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6612                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6613                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6614                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6615                          return 1;
6616                      }
6617                   } else
6618                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6619                   {    /* bare King */
6620                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6621                         if(canAdjudicate && appData.checkMates) {
6622                             /* but only adjudicate if adjudication enabled */
6623                             if(engineOpponent)
6624                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6625                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6627                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6628                             return 1;
6629                         }
6630                   }
6631                 } else bare = 1;
6632
6633
6634             // don't wait for engine to announce game end if we can judge ourselves
6635             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6636               case MT_CHECK:
6637                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6638                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6639                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6640                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6641                             checkCnt++;
6642                         if(checkCnt >= 2) {
6643                             reason = "Xboard adjudication: 3rd check";
6644                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6645                             break;
6646                         }
6647                     }
6648                 }
6649               case MT_NONE:
6650               default:
6651                 break;
6652               case MT_STALEMATE:
6653               case MT_STAINMATE:
6654                 reason = "Xboard adjudication: Stalemate";
6655                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6656                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6657                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6658                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6659                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6660                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6661                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6662                                                                         EP_CHECKMATE : EP_WINS);
6663                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6664                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6665                 }
6666                 break;
6667               case MT_CHECKMATE:
6668                 reason = "Xboard adjudication: Checkmate";
6669                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6670                 break;
6671             }
6672
6673                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6674                     case EP_STALEMATE:
6675                         result = GameIsDrawn; break;
6676                     case EP_CHECKMATE:
6677                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6678                     case EP_WINS:
6679                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6680                     default:
6681                         result = (ChessMove) 0;
6682                 }
6683                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6684                     if(engineOpponent)
6685                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6686                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6687                     GameEnds( result, reason, GE_XBOARD );
6688                     return 1;
6689                 }
6690
6691                 /* Next absolutely insufficient mating material. */
6692                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6693                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6694                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6695                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6696                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6697
6698                      /* always flag draws, for judging claims */
6699                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6700
6701                      if(canAdjudicate && appData.materialDraws) {
6702                          /* but only adjudicate them if adjudication enabled */
6703                          if(engineOpponent) {
6704                            SendToProgram("force\n", engineOpponent); // suppress reply
6705                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6706                          }
6707                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6708                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6709                          return 1;
6710                      }
6711                 }
6712
6713                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6714                 if(NrPieces == 4 && 
6715                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6716                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6717                    || NrWN==2 || NrBN==2     /* KNNK */
6718                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6719                   ) ) {
6720                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6721                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6722                           if(engineOpponent) {
6723                             SendToProgram("force\n", engineOpponent); // suppress reply
6724                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6725                           }
6726                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6727                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6728                           return 1;
6729                      }
6730                 } else moveCount = 6;
6731             }
6732         }
6733           
6734         if (appData.debugMode) { int i;
6735             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6736                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6737                     appData.drawRepeats);
6738             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6739               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6740             
6741         }
6742
6743         // Repetition draws and 50-move rule can be applied independently of legality testing
6744
6745                 /* Check for rep-draws */
6746                 count = 0;
6747                 for(k = forwardMostMove-2;
6748                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6749                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6750                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6751                     k-=2)
6752                 {   int rights=0;
6753                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6754                         /* compare castling rights */
6755                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6756                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6757                                 rights++; /* King lost rights, while rook still had them */
6758                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6759                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6760                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6761                                    rights++; /* but at least one rook lost them */
6762                         }
6763                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6764                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6765                                 rights++; 
6766                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6767                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6768                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6769                                    rights++;
6770                         }
6771                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6772                             && appData.drawRepeats > 1) {
6773                              /* adjudicate after user-specified nr of repeats */
6774                              if(engineOpponent) {
6775                                SendToProgram("force\n", engineOpponent); // suppress reply
6776                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6777                              }
6778                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6779                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6780                                 // [HGM] xiangqi: check for forbidden perpetuals
6781                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6782                                 for(m=forwardMostMove; m>k; m-=2) {
6783                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6784                                         ourPerpetual = 0; // the current mover did not always check
6785                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6786                                         hisPerpetual = 0; // the opponent did not always check
6787                                 }
6788                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6789                                                                         ourPerpetual, hisPerpetual);
6790                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6791                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6792                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6793                                     return 1;
6794                                 }
6795                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6796                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6797                                 // Now check for perpetual chases
6798                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6799                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6800                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6801                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6802                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6803                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6804                                         return 1;
6805                                     }
6806                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6807                                         break; // Abort repetition-checking loop.
6808                                 }
6809                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6810                              }
6811                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6812                              return 1;
6813                         }
6814                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6815                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6816                     }
6817                 }
6818
6819                 /* Now we test for 50-move draws. Determine ply count */
6820                 count = forwardMostMove;
6821                 /* look for last irreversble move */
6822                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6823                     count--;
6824                 /* if we hit starting position, add initial plies */
6825                 if( count == backwardMostMove )
6826                     count -= initialRulePlies;
6827                 count = forwardMostMove - count; 
6828                 if( count >= 100)
6829                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6830                          /* this is used to judge if draw claims are legal */
6831                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6832                          if(engineOpponent) {
6833                            SendToProgram("force\n", engineOpponent); // suppress reply
6834                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6835                          }
6836                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6837                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6838                          return 1;
6839                 }
6840
6841                 /* if draw offer is pending, treat it as a draw claim
6842                  * when draw condition present, to allow engines a way to
6843                  * claim draws before making their move to avoid a race
6844                  * condition occurring after their move
6845                  */
6846                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6847                          char *p = NULL;
6848                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6849                              p = "Draw claim: 50-move rule";
6850                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6851                              p = "Draw claim: 3-fold repetition";
6852                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6853                              p = "Draw claim: insufficient mating material";
6854                          if( p != NULL && canAdjudicate) {
6855                              if(engineOpponent) {
6856                                SendToProgram("force\n", engineOpponent); // suppress reply
6857                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6858                              }
6859                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6860                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6861                              return 1;
6862                          }
6863                 }
6864
6865                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6866                     if(engineOpponent) {
6867                       SendToProgram("force\n", engineOpponent); // suppress reply
6868                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6869                     }
6870                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6871                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6872                     return 1;
6873                 }
6874         return 0;
6875 }
6876
6877 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6878 {   // [HGM] book: this routine intercepts moves to simulate book replies
6879     char *bookHit = NULL;
6880
6881     //first determine if the incoming move brings opponent into his book
6882     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6883         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6884     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6885     if(bookHit != NULL && !cps->bookSuspend) {
6886         // make sure opponent is not going to reply after receiving move to book position
6887         SendToProgram("force\n", cps);
6888         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6889     }
6890     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6891     // now arrange restart after book miss
6892     if(bookHit) {
6893         // after a book hit we never send 'go', and the code after the call to this routine
6894         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6895         char buf[MSG_SIZ];
6896         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6897         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6898         SendToProgram(buf, cps);
6899         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6900     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6901         SendToProgram("go\n", cps);
6902         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6903     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6904         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6905             SendToProgram("go\n", cps); 
6906         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6907     }
6908     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6909 }
6910
6911 char *savedMessage;
6912 ChessProgramState *savedState;
6913 void DeferredBookMove(void)
6914 {
6915         if(savedState->lastPing != savedState->lastPong)
6916                     ScheduleDelayedEvent(DeferredBookMove, 10);
6917         else
6918         HandleMachineMove(savedMessage, savedState);
6919 }
6920
6921 void
6922 HandleMachineMove(message, cps)
6923      char *message;
6924      ChessProgramState *cps;
6925 {
6926     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6927     char realname[MSG_SIZ];
6928     int fromX, fromY, toX, toY;
6929     ChessMove moveType;
6930     char promoChar;
6931     char *p;
6932     int machineWhite;
6933     char *bookHit;
6934
6935     cps->userError = 0;
6936
6937 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6938     /*
6939      * Kludge to ignore BEL characters
6940      */
6941     while (*message == '\007') message++;
6942
6943     /*
6944      * [HGM] engine debug message: ignore lines starting with '#' character
6945      */
6946     if(cps->debug && *message == '#') return;
6947
6948     /*
6949      * Look for book output
6950      */
6951     if (cps == &first && bookRequested) {
6952         if (message[0] == '\t' || message[0] == ' ') {
6953             /* Part of the book output is here; append it */
6954             strcat(bookOutput, message);
6955             strcat(bookOutput, "  \n");
6956             return;
6957         } else if (bookOutput[0] != NULLCHAR) {
6958             /* All of book output has arrived; display it */
6959             char *p = bookOutput;
6960             while (*p != NULLCHAR) {
6961                 if (*p == '\t') *p = ' ';
6962                 p++;
6963             }
6964             DisplayInformation(bookOutput);
6965             bookRequested = FALSE;
6966             /* Fall through to parse the current output */
6967         }
6968     }
6969
6970     /*
6971      * Look for machine move.
6972      */
6973     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6974         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6975     {
6976         /* This method is only useful on engines that support ping */
6977         if (cps->lastPing != cps->lastPong) {
6978           if (gameMode == BeginningOfGame) {
6979             /* Extra move from before last new; ignore */
6980             if (appData.debugMode) {
6981                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6982             }
6983           } else {
6984             if (appData.debugMode) {
6985                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6986                         cps->which, gameMode);
6987             }
6988
6989             SendToProgram("undo\n", cps);
6990           }
6991           return;
6992         }
6993
6994         switch (gameMode) {
6995           case BeginningOfGame:
6996             /* Extra move from before last reset; ignore */
6997             if (appData.debugMode) {
6998                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6999             }
7000             return;
7001
7002           case EndOfGame:
7003           case IcsIdle:
7004           default:
7005             /* Extra move after we tried to stop.  The mode test is
7006                not a reliable way of detecting this problem, but it's
7007                the best we can do on engines that don't support ping.
7008             */
7009             if (appData.debugMode) {
7010                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7011                         cps->which, gameMode);
7012             }
7013             SendToProgram("undo\n", cps);
7014             return;
7015
7016           case MachinePlaysWhite:
7017           case IcsPlayingWhite:
7018             machineWhite = TRUE;
7019             break;
7020
7021           case MachinePlaysBlack:
7022           case IcsPlayingBlack:
7023             machineWhite = FALSE;
7024             break;
7025
7026           case TwoMachinesPlay:
7027             machineWhite = (cps->twoMachinesColor[0] == 'w');
7028             break;
7029         }
7030         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7031             if (appData.debugMode) {
7032                 fprintf(debugFP,
7033                         "Ignoring move out of turn by %s, gameMode %d"
7034                         ", forwardMost %d\n",
7035                         cps->which, gameMode, forwardMostMove);
7036             }
7037             return;
7038         }
7039
7040     if (appData.debugMode) { int f = forwardMostMove;
7041         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7042                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7043                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7044     }
7045         if(cps->alphaRank) AlphaRank(machineMove, 4);
7046         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7047                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7048             /* Machine move could not be parsed; ignore it. */
7049             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7050                     machineMove, cps->which);
7051             DisplayError(buf1, 0);
7052             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7053                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7054             if (gameMode == TwoMachinesPlay) {
7055               GameEnds(machineWhite ? BlackWins : WhiteWins,
7056                        buf1, GE_XBOARD);
7057             }
7058             return;
7059         }
7060
7061         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7062         /* So we have to redo legality test with true e.p. status here,  */
7063         /* to make sure an illegal e.p. capture does not slip through,   */
7064         /* to cause a forfeit on a justified illegal-move complaint      */
7065         /* of the opponent.                                              */
7066         if( gameMode==TwoMachinesPlay && appData.testLegality
7067             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7068                                                               ) {
7069            ChessMove moveType;
7070            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7071                              fromY, fromX, toY, toX, promoChar);
7072             if (appData.debugMode) {
7073                 int i;
7074                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7075                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7076                 fprintf(debugFP, "castling rights\n");
7077             }
7078             if(moveType == IllegalMove) {
7079                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7080                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7081                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7082                            buf1, GE_XBOARD);
7083                 return;
7084            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7085            /* [HGM] Kludge to handle engines that send FRC-style castling
7086               when they shouldn't (like TSCP-Gothic) */
7087            switch(moveType) {
7088              case WhiteASideCastleFR:
7089              case BlackASideCastleFR:
7090                toX+=2;
7091                currentMoveString[2]++;
7092                break;
7093              case WhiteHSideCastleFR:
7094              case BlackHSideCastleFR:
7095                toX--;
7096                currentMoveString[2]--;
7097                break;
7098              default: ; // nothing to do, but suppresses warning of pedantic compilers
7099            }
7100         }
7101         hintRequested = FALSE;
7102         lastHint[0] = NULLCHAR;
7103         bookRequested = FALSE;
7104         /* Program may be pondering now */
7105         cps->maybeThinking = TRUE;
7106         if (cps->sendTime == 2) cps->sendTime = 1;
7107         if (cps->offeredDraw) cps->offeredDraw--;
7108
7109         /* currentMoveString is set as a side-effect of ParseOneMove */
7110         strcpy(machineMove, currentMoveString);
7111         strcat(machineMove, "\n");
7112         strcpy(moveList[forwardMostMove], machineMove);
7113
7114         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7115
7116         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7117         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7118             int count = 0;
7119
7120             while( count < adjudicateLossPlies ) {
7121                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7122
7123                 if( count & 1 ) {
7124                     score = -score; /* Flip score for winning side */
7125                 }
7126
7127                 if( score > adjudicateLossThreshold ) {
7128                     break;
7129                 }
7130
7131                 count++;
7132             }
7133
7134             if( count >= adjudicateLossPlies ) {
7135                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7136
7137                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7138                     "Xboard adjudication", 
7139                     GE_XBOARD );
7140
7141                 return;
7142             }
7143         }
7144
7145         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7146
7147 #if ZIPPY
7148         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7149             first.initDone) {
7150           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7151                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7152                 SendToICS("draw ");
7153                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7154           }
7155           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7156           ics_user_moved = 1;
7157           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7158                 char buf[3*MSG_SIZ];
7159
7160                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7161                         programStats.score / 100.,
7162                         programStats.depth,
7163                         programStats.time / 100.,
7164                         (unsigned int)programStats.nodes,
7165                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7166                         programStats.movelist);
7167                 SendToICS(buf);
7168 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7169           }
7170         }
7171 #endif
7172
7173         /* [AS] Save move info and clear stats for next move */
7174         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7175         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7176         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7177         ClearProgramStats();
7178         thinkOutput[0] = NULLCHAR;
7179         hiddenThinkOutputState = 0;
7180
7181         bookHit = NULL;
7182         if (gameMode == TwoMachinesPlay) {
7183             /* [HGM] relaying draw offers moved to after reception of move */
7184             /* and interpreting offer as claim if it brings draw condition */
7185             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7186                 SendToProgram("draw\n", cps->other);
7187             }
7188             if (cps->other->sendTime) {
7189                 SendTimeRemaining(cps->other,
7190                                   cps->other->twoMachinesColor[0] == 'w');
7191             }
7192             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7193             if (firstMove && !bookHit) {
7194                 firstMove = FALSE;
7195                 if (cps->other->useColors) {
7196                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7197                 }
7198                 SendToProgram("go\n", cps->other);
7199             }
7200             cps->other->maybeThinking = TRUE;
7201         }
7202
7203         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7204         
7205         if (!pausing && appData.ringBellAfterMoves) {
7206             RingBell();
7207         }
7208
7209         /* 
7210          * Reenable menu items that were disabled while
7211          * machine was thinking
7212          */
7213         if (gameMode != TwoMachinesPlay)
7214             SetUserThinkingEnables();
7215
7216         // [HGM] book: after book hit opponent has received move and is now in force mode
7217         // force the book reply into it, and then fake that it outputted this move by jumping
7218         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7219         if(bookHit) {
7220                 static char bookMove[MSG_SIZ]; // a bit generous?
7221
7222                 strcpy(bookMove, "move ");
7223                 strcat(bookMove, bookHit);
7224                 message = bookMove;
7225                 cps = cps->other;
7226                 programStats.nodes = programStats.depth = programStats.time = 
7227                 programStats.score = programStats.got_only_move = 0;
7228                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7229
7230                 if(cps->lastPing != cps->lastPong) {
7231                     savedMessage = message; // args for deferred call
7232                     savedState = cps;
7233                     ScheduleDelayedEvent(DeferredBookMove, 10);
7234                     return;
7235                 }
7236                 goto FakeBookMove;
7237         }
7238
7239         return;
7240     }
7241
7242     /* Set special modes for chess engines.  Later something general
7243      *  could be added here; for now there is just one kludge feature,
7244      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7245      *  when "xboard" is given as an interactive command.
7246      */
7247     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7248         cps->useSigint = FALSE;
7249         cps->useSigterm = FALSE;
7250     }
7251     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7252       ParseFeatures(message+8, cps);
7253       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7254     }
7255
7256     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7257      * want this, I was asked to put it in, and obliged.
7258      */
7259     if (!strncmp(message, "setboard ", 9)) {
7260         Board initial_position;
7261
7262         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7263
7264         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7265             DisplayError(_("Bad FEN received from engine"), 0);
7266             return ;
7267         } else {
7268            Reset(TRUE, FALSE);
7269            CopyBoard(boards[0], initial_position);
7270            initialRulePlies = FENrulePlies;
7271            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7272            else gameMode = MachinePlaysBlack;                 
7273            DrawPosition(FALSE, boards[currentMove]);
7274         }
7275         return;
7276     }
7277
7278     /*
7279      * Look for communication commands
7280      */
7281     if (!strncmp(message, "telluser ", 9)) {
7282         DisplayNote(message + 9);
7283         return;
7284     }
7285     if (!strncmp(message, "tellusererror ", 14)) {
7286         cps->userError = 1;
7287         DisplayError(message + 14, 0);
7288         return;
7289     }
7290     if (!strncmp(message, "tellopponent ", 13)) {
7291       if (appData.icsActive) {
7292         if (loggedOn) {
7293           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7294           SendToICS(buf1);
7295         }
7296       } else {
7297         DisplayNote(message + 13);
7298       }
7299       return;
7300     }
7301     if (!strncmp(message, "tellothers ", 11)) {
7302       if (appData.icsActive) {
7303         if (loggedOn) {
7304           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7305           SendToICS(buf1);
7306         }
7307       }
7308       return;
7309     }
7310     if (!strncmp(message, "tellall ", 8)) {
7311       if (appData.icsActive) {
7312         if (loggedOn) {
7313           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7314           SendToICS(buf1);
7315         }
7316       } else {
7317         DisplayNote(message + 8);
7318       }
7319       return;
7320     }
7321     if (strncmp(message, "warning", 7) == 0) {
7322         /* Undocumented feature, use tellusererror in new code */
7323         DisplayError(message, 0);
7324         return;
7325     }
7326     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7327         strcpy(realname, cps->tidy);
7328         strcat(realname, " query");
7329         AskQuestion(realname, buf2, buf1, cps->pr);
7330         return;
7331     }
7332     /* Commands from the engine directly to ICS.  We don't allow these to be 
7333      *  sent until we are logged on. Crafty kibitzes have been known to 
7334      *  interfere with the login process.
7335      */
7336     if (loggedOn) {
7337         if (!strncmp(message, "tellics ", 8)) {
7338             SendToICS(message + 8);
7339             SendToICS("\n");
7340             return;
7341         }
7342         if (!strncmp(message, "tellicsnoalias ", 15)) {
7343             SendToICS(ics_prefix);
7344             SendToICS(message + 15);
7345             SendToICS("\n");
7346             return;
7347         }
7348         /* The following are for backward compatibility only */
7349         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7350             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7351             SendToICS(ics_prefix);
7352             SendToICS(message);
7353             SendToICS("\n");
7354             return;
7355         }
7356     }
7357     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7358         return;
7359     }
7360     /*
7361      * If the move is illegal, cancel it and redraw the board.
7362      * Also deal with other error cases.  Matching is rather loose
7363      * here to accommodate engines written before the spec.
7364      */
7365     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7366         strncmp(message, "Error", 5) == 0) {
7367         if (StrStr(message, "name") || 
7368             StrStr(message, "rating") || StrStr(message, "?") ||
7369             StrStr(message, "result") || StrStr(message, "board") ||
7370             StrStr(message, "bk") || StrStr(message, "computer") ||
7371             StrStr(message, "variant") || StrStr(message, "hint") ||
7372             StrStr(message, "random") || StrStr(message, "depth") ||
7373             StrStr(message, "accepted")) {
7374             return;
7375         }
7376         if (StrStr(message, "protover")) {
7377           /* Program is responding to input, so it's apparently done
7378              initializing, and this error message indicates it is
7379              protocol version 1.  So we don't need to wait any longer
7380              for it to initialize and send feature commands. */
7381           FeatureDone(cps, 1);
7382           cps->protocolVersion = 1;
7383           return;
7384         }
7385         cps->maybeThinking = FALSE;
7386
7387         if (StrStr(message, "draw")) {
7388             /* Program doesn't have "draw" command */
7389             cps->sendDrawOffers = 0;
7390             return;
7391         }
7392         if (cps->sendTime != 1 &&
7393             (StrStr(message, "time") || StrStr(message, "otim"))) {
7394           /* Program apparently doesn't have "time" or "otim" command */
7395           cps->sendTime = 0;
7396           return;
7397         }
7398         if (StrStr(message, "analyze")) {
7399             cps->analysisSupport = FALSE;
7400             cps->analyzing = FALSE;
7401             Reset(FALSE, TRUE);
7402             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7403             DisplayError(buf2, 0);
7404             return;
7405         }
7406         if (StrStr(message, "(no matching move)st")) {
7407           /* Special kludge for GNU Chess 4 only */
7408           cps->stKludge = TRUE;
7409           SendTimeControl(cps, movesPerSession, timeControl,
7410                           timeIncrement, appData.searchDepth,
7411                           searchTime);
7412           return;
7413         }
7414         if (StrStr(message, "(no matching move)sd")) {
7415           /* Special kludge for GNU Chess 4 only */
7416           cps->sdKludge = TRUE;
7417           SendTimeControl(cps, movesPerSession, timeControl,
7418                           timeIncrement, appData.searchDepth,
7419                           searchTime);
7420           return;
7421         }
7422         if (!StrStr(message, "llegal")) {
7423             return;
7424         }
7425         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7426             gameMode == IcsIdle) return;
7427         if (forwardMostMove <= backwardMostMove) return;
7428         if (pausing) PauseEvent();
7429       if(appData.forceIllegal) {
7430             // [HGM] illegal: machine refused move; force position after move into it
7431           SendToProgram("force\n", cps);
7432           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7433                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7434                 // when black is to move, while there might be nothing on a2 or black
7435                 // might already have the move. So send the board as if white has the move.
7436                 // But first we must change the stm of the engine, as it refused the last move
7437                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7438                 if(WhiteOnMove(forwardMostMove)) {
7439                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7440                     SendBoard(cps, forwardMostMove); // kludgeless board
7441                 } else {
7442                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7443                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7444                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7445                 }
7446           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7447             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7448                  gameMode == TwoMachinesPlay)
7449               SendToProgram("go\n", cps);
7450             return;
7451       } else
7452         if (gameMode == PlayFromGameFile) {
7453             /* Stop reading this game file */
7454             gameMode = EditGame;
7455             ModeHighlight();
7456         }
7457         currentMove = forwardMostMove-1;
7458         DisplayMove(currentMove-1); /* before DisplayMoveError */
7459         SwitchClocks(forwardMostMove-1); // [HGM] race
7460         DisplayBothClocks();
7461         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7462                 parseList[currentMove], cps->which);
7463         DisplayMoveError(buf1);
7464         DrawPosition(FALSE, boards[currentMove]);
7465
7466         /* [HGM] illegal-move claim should forfeit game when Xboard */
7467         /* only passes fully legal moves                            */
7468         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7469             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7470                                 "False illegal-move claim", GE_XBOARD );
7471         }
7472         return;
7473     }
7474     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7475         /* Program has a broken "time" command that
7476            outputs a string not ending in newline.
7477            Don't use it. */
7478         cps->sendTime = 0;
7479     }
7480     
7481     /*
7482      * If chess program startup fails, exit with an error message.
7483      * Attempts to recover here are futile.
7484      */
7485     if ((StrStr(message, "unknown host") != NULL)
7486         || (StrStr(message, "No remote directory") != NULL)
7487         || (StrStr(message, "not found") != NULL)
7488         || (StrStr(message, "No such file") != NULL)
7489         || (StrStr(message, "can't alloc") != NULL)
7490         || (StrStr(message, "Permission denied") != NULL)) {
7491
7492         cps->maybeThinking = FALSE;
7493         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7494                 cps->which, cps->program, cps->host, message);
7495         RemoveInputSource(cps->isr);
7496         DisplayFatalError(buf1, 0, 1);
7497         return;
7498     }
7499     
7500     /* 
7501      * Look for hint output
7502      */
7503     if (sscanf(message, "Hint: %s", buf1) == 1) {
7504         if (cps == &first && hintRequested) {
7505             hintRequested = FALSE;
7506             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7507                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7508                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7509                                     PosFlags(forwardMostMove),
7510                                     fromY, fromX, toY, toX, promoChar, buf1);
7511                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7512                 DisplayInformation(buf2);
7513             } else {
7514                 /* Hint move could not be parsed!? */
7515               snprintf(buf2, sizeof(buf2),
7516                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7517                         buf1, cps->which);
7518                 DisplayError(buf2, 0);
7519             }
7520         } else {
7521             strcpy(lastHint, buf1);
7522         }
7523         return;
7524     }
7525
7526     /*
7527      * Ignore other messages if game is not in progress
7528      */
7529     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7530         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7531
7532     /*
7533      * look for win, lose, draw, or draw offer
7534      */
7535     if (strncmp(message, "1-0", 3) == 0) {
7536         char *p, *q, *r = "";
7537         p = strchr(message, '{');
7538         if (p) {
7539             q = strchr(p, '}');
7540             if (q) {
7541                 *q = NULLCHAR;
7542                 r = p + 1;
7543             }
7544         }
7545         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7546         return;
7547     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
7558         if (strcmp(r, "Black resigns") == 0) {
7559             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7560             return;
7561         }
7562         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7563         return;
7564     } else if (strncmp(message, "1/2", 3) == 0) {
7565         char *p, *q, *r = "";
7566         p = strchr(message, '{');
7567         if (p) {
7568             q = strchr(p, '}');
7569             if (q) {
7570                 *q = NULLCHAR;
7571                 r = p + 1;
7572             }
7573         }
7574             
7575         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7576         return;
7577
7578     } else if (strncmp(message, "White resign", 12) == 0) {
7579         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7580         return;
7581     } else if (strncmp(message, "Black resign", 12) == 0) {
7582         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7583         return;
7584     } else if (strncmp(message, "White matches", 13) == 0 ||
7585                strncmp(message, "Black matches", 13) == 0   ) {
7586         /* [HGM] ignore GNUShogi noises */
7587         return;
7588     } else if (strncmp(message, "White", 5) == 0 &&
7589                message[5] != '(' &&
7590                StrStr(message, "Black") == NULL) {
7591         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7592         return;
7593     } else if (strncmp(message, "Black", 5) == 0 &&
7594                message[5] != '(') {
7595         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7596         return;
7597     } else if (strcmp(message, "resign") == 0 ||
7598                strcmp(message, "computer resigns") == 0) {
7599         switch (gameMode) {
7600           case MachinePlaysBlack:
7601           case IcsPlayingBlack:
7602             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7603             break;
7604           case MachinePlaysWhite:
7605           case IcsPlayingWhite:
7606             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7607             break;
7608           case TwoMachinesPlay:
7609             if (cps->twoMachinesColor[0] == 'w')
7610               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7611             else
7612               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7613             break;
7614           default:
7615             /* can't happen */
7616             break;
7617         }
7618         return;
7619     } else if (strncmp(message, "opponent mates", 14) == 0) {
7620         switch (gameMode) {
7621           case MachinePlaysBlack:
7622           case IcsPlayingBlack:
7623             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7624             break;
7625           case MachinePlaysWhite:
7626           case IcsPlayingWhite:
7627             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7628             break;
7629           case TwoMachinesPlay:
7630             if (cps->twoMachinesColor[0] == 'w')
7631               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7632             else
7633               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7634             break;
7635           default:
7636             /* can't happen */
7637             break;
7638         }
7639         return;
7640     } else if (strncmp(message, "computer mates", 14) == 0) {
7641         switch (gameMode) {
7642           case MachinePlaysBlack:
7643           case IcsPlayingBlack:
7644             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7645             break;
7646           case MachinePlaysWhite:
7647           case IcsPlayingWhite:
7648             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7649             break;
7650           case TwoMachinesPlay:
7651             if (cps->twoMachinesColor[0] == 'w')
7652               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7653             else
7654               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7655             break;
7656           default:
7657             /* can't happen */
7658             break;
7659         }
7660         return;
7661     } else if (strncmp(message, "checkmate", 9) == 0) {
7662         if (WhiteOnMove(forwardMostMove)) {
7663             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7664         } else {
7665             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7666         }
7667         return;
7668     } else if (strstr(message, "Draw") != NULL ||
7669                strstr(message, "game is a draw") != NULL) {
7670         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7671         return;
7672     } else if (strstr(message, "offer") != NULL &&
7673                strstr(message, "draw") != NULL) {
7674 #if ZIPPY
7675         if (appData.zippyPlay && first.initDone) {
7676             /* Relay offer to ICS */
7677             SendToICS(ics_prefix);
7678             SendToICS("draw\n");
7679         }
7680 #endif
7681         cps->offeredDraw = 2; /* valid until this engine moves twice */
7682         if (gameMode == TwoMachinesPlay) {
7683             if (cps->other->offeredDraw) {
7684                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7685             /* [HGM] in two-machine mode we delay relaying draw offer      */
7686             /* until after we also have move, to see if it is really claim */
7687             }
7688         } else if (gameMode == MachinePlaysWhite ||
7689                    gameMode == MachinePlaysBlack) {
7690           if (userOfferedDraw) {
7691             DisplayInformation(_("Machine accepts your draw offer"));
7692             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7693           } else {
7694             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7695           }
7696         }
7697     }
7698
7699     
7700     /*
7701      * Look for thinking output
7702      */
7703     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7704           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7705                                 ) {
7706         int plylev, mvleft, mvtot, curscore, time;
7707         char mvname[MOVE_LEN];
7708         u64 nodes; // [DM]
7709         char plyext;
7710         int ignore = FALSE;
7711         int prefixHint = FALSE;
7712         mvname[0] = NULLCHAR;
7713
7714         switch (gameMode) {
7715           case MachinePlaysBlack:
7716           case IcsPlayingBlack:
7717             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7718             break;
7719           case MachinePlaysWhite:
7720           case IcsPlayingWhite:
7721             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7722             break;
7723           case AnalyzeMode:
7724           case AnalyzeFile:
7725             break;
7726           case IcsObserving: /* [DM] icsEngineAnalyze */
7727             if (!appData.icsEngineAnalyze) ignore = TRUE;
7728             break;
7729           case TwoMachinesPlay:
7730             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7731                 ignore = TRUE;
7732             }
7733             break;
7734           default:
7735             ignore = TRUE;
7736             break;
7737         }
7738
7739         if (!ignore) {
7740             buf1[0] = NULLCHAR;
7741             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7742                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7743
7744                 if (plyext != ' ' && plyext != '\t') {
7745                     time *= 100;
7746                 }
7747
7748                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7749                 if( cps->scoreIsAbsolute && 
7750                     ( gameMode == MachinePlaysBlack ||
7751                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7752                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7753                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7754                      !WhiteOnMove(currentMove)
7755                     ) )
7756                 {
7757                     curscore = -curscore;
7758                 }
7759
7760
7761                 programStats.depth = plylev;
7762                 programStats.nodes = nodes;
7763                 programStats.time = time;
7764                 programStats.score = curscore;
7765                 programStats.got_only_move = 0;
7766
7767                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7768                         int ticklen;
7769
7770                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7771                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7772                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7773                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7774                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7775                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7776                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7777                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7778                 }
7779
7780                 /* Buffer overflow protection */
7781                 if (buf1[0] != NULLCHAR) {
7782                     if (strlen(buf1) >= sizeof(programStats.movelist)
7783                         && appData.debugMode) {
7784                         fprintf(debugFP,
7785                                 "PV is too long; using the first %u bytes.\n",
7786                                 (unsigned) sizeof(programStats.movelist) - 1);
7787                     }
7788
7789                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7790                 } else {
7791                     sprintf(programStats.movelist, " no PV\n");
7792                 }
7793
7794                 if (programStats.seen_stat) {
7795                     programStats.ok_to_send = 1;
7796                 }
7797
7798                 if (strchr(programStats.movelist, '(') != NULL) {
7799                     programStats.line_is_book = 1;
7800                     programStats.nr_moves = 0;
7801                     programStats.moves_left = 0;
7802                 } else {
7803                     programStats.line_is_book = 0;
7804                 }
7805
7806                 SendProgramStatsToFrontend( cps, &programStats );
7807
7808                 /* 
7809                     [AS] Protect the thinkOutput buffer from overflow... this
7810                     is only useful if buf1 hasn't overflowed first!
7811                 */
7812                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7813                         plylev, 
7814                         (gameMode == TwoMachinesPlay ?
7815                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7816                         ((double) curscore) / 100.0,
7817                         prefixHint ? lastHint : "",
7818                         prefixHint ? " " : "" );
7819
7820                 if( buf1[0] != NULLCHAR ) {
7821                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7822
7823                     if( strlen(buf1) > max_len ) {
7824                         if( appData.debugMode) {
7825                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7826                         }
7827                         buf1[max_len+1] = '\0';
7828                     }
7829
7830                     strcat( thinkOutput, buf1 );
7831                 }
7832
7833                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7834                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7835                     DisplayMove(currentMove - 1);
7836                 }
7837                 return;
7838
7839             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7840                 /* crafty (9.25+) says "(only move) <move>"
7841                  * if there is only 1 legal move
7842                  */
7843                 sscanf(p, "(only move) %s", buf1);
7844                 sprintf(thinkOutput, "%s (only move)", buf1);
7845                 sprintf(programStats.movelist, "%s (only move)", buf1);
7846                 programStats.depth = 1;
7847                 programStats.nr_moves = 1;
7848                 programStats.moves_left = 1;
7849                 programStats.nodes = 1;
7850                 programStats.time = 1;
7851                 programStats.got_only_move = 1;
7852
7853                 /* Not really, but we also use this member to
7854                    mean "line isn't going to change" (Crafty
7855                    isn't searching, so stats won't change) */
7856                 programStats.line_is_book = 1;
7857
7858                 SendProgramStatsToFrontend( cps, &programStats );
7859                 
7860                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7861                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7862                     DisplayMove(currentMove - 1);
7863                 }
7864                 return;
7865             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7866                               &time, &nodes, &plylev, &mvleft,
7867                               &mvtot, mvname) >= 5) {
7868                 /* The stat01: line is from Crafty (9.29+) in response
7869                    to the "." command */
7870                 programStats.seen_stat = 1;
7871                 cps->maybeThinking = TRUE;
7872
7873                 if (programStats.got_only_move || !appData.periodicUpdates)
7874                   return;
7875
7876                 programStats.depth = plylev;
7877                 programStats.time = time;
7878                 programStats.nodes = nodes;
7879                 programStats.moves_left = mvleft;
7880                 programStats.nr_moves = mvtot;
7881                 strcpy(programStats.move_name, mvname);
7882                 programStats.ok_to_send = 1;
7883                 programStats.movelist[0] = '\0';
7884
7885                 SendProgramStatsToFrontend( cps, &programStats );
7886
7887                 return;
7888
7889             } else if (strncmp(message,"++",2) == 0) {
7890                 /* Crafty 9.29+ outputs this */
7891                 programStats.got_fail = 2;
7892                 return;
7893
7894             } else if (strncmp(message,"--",2) == 0) {
7895                 /* Crafty 9.29+ outputs this */
7896                 programStats.got_fail = 1;
7897                 return;
7898
7899             } else if (thinkOutput[0] != NULLCHAR &&
7900                        strncmp(message, "    ", 4) == 0) {
7901                 unsigned message_len;
7902
7903                 p = message;
7904                 while (*p && *p == ' ') p++;
7905
7906                 message_len = strlen( p );
7907
7908                 /* [AS] Avoid buffer overflow */
7909                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7910                     strcat(thinkOutput, " ");
7911                     strcat(thinkOutput, p);
7912                 }
7913
7914                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7915                     strcat(programStats.movelist, " ");
7916                     strcat(programStats.movelist, p);
7917                 }
7918
7919                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7920                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7921                     DisplayMove(currentMove - 1);
7922                 }
7923                 return;
7924             }
7925         }
7926         else {
7927             buf1[0] = NULLCHAR;
7928
7929             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7930                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7931             {
7932                 ChessProgramStats cpstats;
7933
7934                 if (plyext != ' ' && plyext != '\t') {
7935                     time *= 100;
7936                 }
7937
7938                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7939                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7940                     curscore = -curscore;
7941                 }
7942
7943                 cpstats.depth = plylev;
7944                 cpstats.nodes = nodes;
7945                 cpstats.time = time;
7946                 cpstats.score = curscore;
7947                 cpstats.got_only_move = 0;
7948                 cpstats.movelist[0] = '\0';
7949
7950                 if (buf1[0] != NULLCHAR) {
7951                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7952                 }
7953
7954                 cpstats.ok_to_send = 0;
7955                 cpstats.line_is_book = 0;
7956                 cpstats.nr_moves = 0;
7957                 cpstats.moves_left = 0;
7958
7959                 SendProgramStatsToFrontend( cps, &cpstats );
7960             }
7961         }
7962     }
7963 }
7964
7965
7966 /* Parse a game score from the character string "game", and
7967    record it as the history of the current game.  The game
7968    score is NOT assumed to start from the standard position. 
7969    The display is not updated in any way.
7970    */
7971 void
7972 ParseGameHistory(game)
7973      char *game;
7974 {
7975     ChessMove moveType;
7976     int fromX, fromY, toX, toY, boardIndex;
7977     char promoChar;
7978     char *p, *q;
7979     char buf[MSG_SIZ];
7980
7981     if (appData.debugMode)
7982       fprintf(debugFP, "Parsing game history: %s\n", game);
7983
7984     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7985     gameInfo.site = StrSave(appData.icsHost);
7986     gameInfo.date = PGNDate();
7987     gameInfo.round = StrSave("-");
7988
7989     /* Parse out names of players */
7990     while (*game == ' ') game++;
7991     p = buf;
7992     while (*game != ' ') *p++ = *game++;
7993     *p = NULLCHAR;
7994     gameInfo.white = StrSave(buf);
7995     while (*game == ' ') game++;
7996     p = buf;
7997     while (*game != ' ' && *game != '\n') *p++ = *game++;
7998     *p = NULLCHAR;
7999     gameInfo.black = StrSave(buf);
8000
8001     /* Parse moves */
8002     boardIndex = blackPlaysFirst ? 1 : 0;
8003     yynewstr(game);
8004     for (;;) {
8005         yyboardindex = boardIndex;
8006         moveType = (ChessMove) yylex();
8007         switch (moveType) {
8008           case IllegalMove:             /* maybe suicide chess, etc. */
8009   if (appData.debugMode) {
8010     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8011     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8012     setbuf(debugFP, NULL);
8013   }
8014           case WhitePromotionChancellor:
8015           case BlackPromotionChancellor:
8016           case WhitePromotionArchbishop:
8017           case BlackPromotionArchbishop:
8018           case WhitePromotionQueen:
8019           case BlackPromotionQueen:
8020           case WhitePromotionRook:
8021           case BlackPromotionRook:
8022           case WhitePromotionBishop:
8023           case BlackPromotionBishop:
8024           case WhitePromotionKnight:
8025           case BlackPromotionKnight:
8026           case WhitePromotionKing:
8027           case BlackPromotionKing:
8028           case NormalMove:
8029           case WhiteCapturesEnPassant:
8030           case BlackCapturesEnPassant:
8031           case WhiteKingSideCastle:
8032           case WhiteQueenSideCastle:
8033           case BlackKingSideCastle:
8034           case BlackQueenSideCastle:
8035           case WhiteKingSideCastleWild:
8036           case WhiteQueenSideCastleWild:
8037           case BlackKingSideCastleWild:
8038           case BlackQueenSideCastleWild:
8039           /* PUSH Fabien */
8040           case WhiteHSideCastleFR:
8041           case WhiteASideCastleFR:
8042           case BlackHSideCastleFR:
8043           case BlackASideCastleFR:
8044           /* POP Fabien */
8045             fromX = currentMoveString[0] - AAA;
8046             fromY = currentMoveString[1] - ONE;
8047             toX = currentMoveString[2] - AAA;
8048             toY = currentMoveString[3] - ONE;
8049             promoChar = currentMoveString[4];
8050             break;
8051           case WhiteDrop:
8052           case BlackDrop:
8053             fromX = moveType == WhiteDrop ?
8054               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8055             (int) CharToPiece(ToLower(currentMoveString[0]));
8056             fromY = DROP_RANK;
8057             toX = currentMoveString[2] - AAA;
8058             toY = currentMoveString[3] - ONE;
8059             promoChar = NULLCHAR;
8060             break;
8061           case AmbiguousMove:
8062             /* bug? */
8063             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8064   if (appData.debugMode) {
8065     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8066     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8067     setbuf(debugFP, NULL);
8068   }
8069             DisplayError(buf, 0);
8070             return;
8071           case ImpossibleMove:
8072             /* bug? */
8073             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8074   if (appData.debugMode) {
8075     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8076     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8077     setbuf(debugFP, NULL);
8078   }
8079             DisplayError(buf, 0);
8080             return;
8081           case (ChessMove) 0:   /* end of file */
8082             if (boardIndex < backwardMostMove) {
8083                 /* Oops, gap.  How did that happen? */
8084                 DisplayError(_("Gap in move list"), 0);
8085                 return;
8086             }
8087             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8088             if (boardIndex > forwardMostMove) {
8089                 forwardMostMove = boardIndex;
8090             }
8091             return;
8092           case ElapsedTime:
8093             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8094                 strcat(parseList[boardIndex-1], " ");
8095                 strcat(parseList[boardIndex-1], yy_text);
8096             }
8097             continue;
8098           case Comment:
8099           case PGNTag:
8100           case NAG:
8101           default:
8102             /* ignore */
8103             continue;
8104           case WhiteWins:
8105           case BlackWins:
8106           case GameIsDrawn:
8107           case GameUnfinished:
8108             if (gameMode == IcsExamining) {
8109                 if (boardIndex < backwardMostMove) {
8110                     /* Oops, gap.  How did that happen? */
8111                     return;
8112                 }
8113                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8114                 return;
8115             }
8116             gameInfo.result = moveType;
8117             p = strchr(yy_text, '{');
8118             if (p == NULL) p = strchr(yy_text, '(');
8119             if (p == NULL) {
8120                 p = yy_text;
8121                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8122             } else {
8123                 q = strchr(p, *p == '{' ? '}' : ')');
8124                 if (q != NULL) *q = NULLCHAR;
8125                 p++;
8126             }
8127             gameInfo.resultDetails = StrSave(p);
8128             continue;
8129         }
8130         if (boardIndex >= forwardMostMove &&
8131             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8132             backwardMostMove = blackPlaysFirst ? 1 : 0;
8133             return;
8134         }
8135         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8136                                  fromY, fromX, toY, toX, promoChar,
8137                                  parseList[boardIndex]);
8138         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8139         /* currentMoveString is set as a side-effect of yylex */
8140         strcpy(moveList[boardIndex], currentMoveString);
8141         strcat(moveList[boardIndex], "\n");
8142         boardIndex++;
8143         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8144         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8145           case MT_NONE:
8146           case MT_STALEMATE:
8147           default:
8148             break;
8149           case MT_CHECK:
8150             if(gameInfo.variant != VariantShogi)
8151                 strcat(parseList[boardIndex - 1], "+");
8152             break;
8153           case MT_CHECKMATE:
8154           case MT_STAINMATE:
8155             strcat(parseList[boardIndex - 1], "#");
8156             break;
8157         }
8158     }
8159 }
8160
8161
8162 /* Apply a move to the given board  */
8163 void
8164 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8165      int fromX, fromY, toX, toY;
8166      int promoChar;
8167      Board board;
8168 {
8169   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8170   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8171
8172     /* [HGM] compute & store e.p. status and castling rights for new position */
8173     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8174     { int i;
8175
8176       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8177       oldEP = (signed char)board[EP_STATUS];
8178       board[EP_STATUS] = EP_NONE;
8179
8180       if( board[toY][toX] != EmptySquare ) 
8181            board[EP_STATUS] = EP_CAPTURE;  
8182
8183       if( board[fromY][fromX] == WhitePawn ) {
8184            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8185                board[EP_STATUS] = EP_PAWN_MOVE;
8186            if( toY-fromY==2) {
8187                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8188                         gameInfo.variant != VariantBerolina || toX < fromX)
8189                       board[EP_STATUS] = toX | berolina;
8190                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8191                         gameInfo.variant != VariantBerolina || toX > fromX) 
8192                       board[EP_STATUS] = toX;
8193            }
8194       } else 
8195       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
8200                         gameInfo.variant != VariantBerolina || toX < fromX)
8201                       board[EP_STATUS] = toX | berolina;
8202                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8203                         gameInfo.variant != VariantBerolina || toX > fromX) 
8204                       board[EP_STATUS] = toX;
8205            }
8206        }
8207
8208        for(i=0; i<nrCastlingRights; i++) {
8209            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8210               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8211              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8212        }
8213
8214     }
8215
8216   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8217   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8218        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8219          
8220   if (fromX == toX && fromY == toY) return;
8221
8222   if (fromY == DROP_RANK) {
8223         /* must be first */
8224         piece = board[toY][toX] = (ChessSquare) fromX;
8225   } else {
8226      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8227      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8228      if(gameInfo.variant == VariantKnightmate)
8229          king += (int) WhiteUnicorn - (int) WhiteKing;
8230
8231     /* Code added by Tord: */
8232     /* FRC castling assumed when king captures friendly rook. */
8233     if (board[fromY][fromX] == WhiteKing &&
8234              board[toY][toX] == WhiteRook) {
8235       board[fromY][fromX] = EmptySquare;
8236       board[toY][toX] = EmptySquare;
8237       if(toX > fromX) {
8238         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8239       } else {
8240         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8241       }
8242     } else if (board[fromY][fromX] == BlackKing &&
8243                board[toY][toX] == BlackRook) {
8244       board[fromY][fromX] = EmptySquare;
8245       board[toY][toX] = EmptySquare;
8246       if(toX > fromX) {
8247         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8248       } else {
8249         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8250       }
8251     /* End of code added by Tord */
8252
8253     } else if (board[fromY][fromX] == king
8254         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8255         && toY == fromY && toX > fromX+1) {
8256         board[fromY][fromX] = EmptySquare;
8257         board[toY][toX] = king;
8258         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8259         board[fromY][BOARD_RGHT-1] = EmptySquare;
8260     } else if (board[fromY][fromX] == king
8261         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8262                && toY == fromY && toX < fromX-1) {
8263         board[fromY][fromX] = EmptySquare;
8264         board[toY][toX] = king;
8265         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8266         board[fromY][BOARD_LEFT] = EmptySquare;
8267     } else if (board[fromY][fromX] == WhitePawn
8268                && toY >= BOARD_HEIGHT-promoRank
8269                && gameInfo.variant != VariantXiangqi
8270                ) {
8271         /* white pawn promotion */
8272         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8273         if (board[toY][toX] == EmptySquare) {
8274             board[toY][toX] = WhiteQueen;
8275         }
8276         if(gameInfo.variant==VariantBughouse ||
8277            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8278             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8279         board[fromY][fromX] = EmptySquare;
8280     } else if ((fromY == BOARD_HEIGHT-4)
8281                && (toX != fromX)
8282                && gameInfo.variant != VariantXiangqi
8283                && gameInfo.variant != VariantBerolina
8284                && (board[fromY][fromX] == WhitePawn)
8285                && (board[toY][toX] == EmptySquare)) {
8286         board[fromY][fromX] = EmptySquare;
8287         board[toY][toX] = WhitePawn;
8288         captured = board[toY - 1][toX];
8289         board[toY - 1][toX] = EmptySquare;
8290     } else if ((fromY == BOARD_HEIGHT-4)
8291                && (toX == fromX)
8292                && gameInfo.variant == VariantBerolina
8293                && (board[fromY][fromX] == WhitePawn)
8294                && (board[toY][toX] == EmptySquare)) {
8295         board[fromY][fromX] = EmptySquare;
8296         board[toY][toX] = WhitePawn;
8297         if(oldEP & EP_BEROLIN_A) {
8298                 captured = board[fromY][fromX-1];
8299                 board[fromY][fromX-1] = EmptySquare;
8300         }else{  captured = board[fromY][fromX+1];
8301                 board[fromY][fromX+1] = EmptySquare;
8302         }
8303     } else if (board[fromY][fromX] == king
8304         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8305                && toY == fromY && toX > fromX+1) {
8306         board[fromY][fromX] = EmptySquare;
8307         board[toY][toX] = king;
8308         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8309         board[fromY][BOARD_RGHT-1] = EmptySquare;
8310     } else if (board[fromY][fromX] == king
8311         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8312                && toY == fromY && toX < fromX-1) {
8313         board[fromY][fromX] = EmptySquare;
8314         board[toY][toX] = king;
8315         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8316         board[fromY][BOARD_LEFT] = EmptySquare;
8317     } else if (fromY == 7 && fromX == 3
8318                && board[fromY][fromX] == BlackKing
8319                && toY == 7 && toX == 5) {
8320         board[fromY][fromX] = EmptySquare;
8321         board[toY][toX] = BlackKing;
8322         board[fromY][7] = EmptySquare;
8323         board[toY][4] = BlackRook;
8324     } else if (fromY == 7 && fromX == 3
8325                && board[fromY][fromX] == BlackKing
8326                && toY == 7 && toX == 1) {
8327         board[fromY][fromX] = EmptySquare;
8328         board[toY][toX] = BlackKing;
8329         board[fromY][0] = EmptySquare;
8330         board[toY][2] = BlackRook;
8331     } else if (board[fromY][fromX] == BlackPawn
8332                && toY < promoRank
8333                && gameInfo.variant != VariantXiangqi
8334                ) {
8335         /* black pawn promotion */
8336         board[toY][toX] = CharToPiece(ToLower(promoChar));
8337         if (board[toY][toX] == EmptySquare) {
8338             board[toY][toX] = BlackQueen;
8339         }
8340         if(gameInfo.variant==VariantBughouse ||
8341            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8342             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8343         board[fromY][fromX] = EmptySquare;
8344     } else if ((fromY == 3)
8345                && (toX != fromX)
8346                && gameInfo.variant != VariantXiangqi
8347                && gameInfo.variant != VariantBerolina
8348                && (board[fromY][fromX] == BlackPawn)
8349                && (board[toY][toX] == EmptySquare)) {
8350         board[fromY][fromX] = EmptySquare;
8351         board[toY][toX] = BlackPawn;
8352         captured = board[toY + 1][toX];
8353         board[toY + 1][toX] = EmptySquare;
8354     } else if ((fromY == 3)
8355                && (toX == fromX)
8356                && gameInfo.variant == VariantBerolina
8357                && (board[fromY][fromX] == BlackPawn)
8358                && (board[toY][toX] == EmptySquare)) {
8359         board[fromY][fromX] = EmptySquare;
8360         board[toY][toX] = BlackPawn;
8361         if(oldEP & EP_BEROLIN_A) {
8362                 captured = board[fromY][fromX-1];
8363                 board[fromY][fromX-1] = EmptySquare;
8364         }else{  captured = board[fromY][fromX+1];
8365                 board[fromY][fromX+1] = EmptySquare;
8366         }
8367     } else {
8368         board[toY][toX] = board[fromY][fromX];
8369         board[fromY][fromX] = EmptySquare;
8370     }
8371
8372     /* [HGM] now we promote for Shogi, if needed */
8373     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8374         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8375   }
8376
8377     if (gameInfo.holdingsWidth != 0) {
8378
8379       /* !!A lot more code needs to be written to support holdings  */
8380       /* [HGM] OK, so I have written it. Holdings are stored in the */
8381       /* penultimate board files, so they are automaticlly stored   */
8382       /* in the game history.                                       */
8383       if (fromY == DROP_RANK) {
8384         /* Delete from holdings, by decreasing count */
8385         /* and erasing image if necessary            */
8386         p = (int) fromX;
8387         if(p < (int) BlackPawn) { /* white drop */
8388              p -= (int)WhitePawn;
8389                  p = PieceToNumber((ChessSquare)p);
8390              if(p >= gameInfo.holdingsSize) p = 0;
8391              if(--board[p][BOARD_WIDTH-2] <= 0)
8392                   board[p][BOARD_WIDTH-1] = EmptySquare;
8393              if((int)board[p][BOARD_WIDTH-2] < 0)
8394                         board[p][BOARD_WIDTH-2] = 0;
8395         } else {                  /* black drop */
8396              p -= (int)BlackPawn;
8397                  p = PieceToNumber((ChessSquare)p);
8398              if(p >= gameInfo.holdingsSize) p = 0;
8399              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8400                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8401              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8402                         board[BOARD_HEIGHT-1-p][1] = 0;
8403         }
8404       }
8405       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8406           && gameInfo.variant != VariantBughouse        ) {
8407         /* [HGM] holdings: Add to holdings, if holdings exist */
8408         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8409                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8410                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8411         }
8412         p = (int) captured;
8413         if (p >= (int) BlackPawn) {
8414           p -= (int)BlackPawn;
8415           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8416                   /* in Shogi restore piece to its original  first */
8417                   captured = (ChessSquare) (DEMOTED captured);
8418                   p = DEMOTED p;
8419           }
8420           p = PieceToNumber((ChessSquare)p);
8421           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8422           board[p][BOARD_WIDTH-2]++;
8423           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8424         } else {
8425           p -= (int)WhitePawn;
8426           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8427                   captured = (ChessSquare) (DEMOTED captured);
8428                   p = DEMOTED p;
8429           }
8430           p = PieceToNumber((ChessSquare)p);
8431           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8432           board[BOARD_HEIGHT-1-p][1]++;
8433           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8434         }
8435       }
8436     } else if (gameInfo.variant == VariantAtomic) {
8437       if (captured != EmptySquare) {
8438         int y, x;
8439         for (y = toY-1; y <= toY+1; y++) {
8440           for (x = toX-1; x <= toX+1; x++) {
8441             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8442                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8443               board[y][x] = EmptySquare;
8444             }
8445           }
8446         }
8447         board[toY][toX] = EmptySquare;
8448       }
8449     }
8450     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8451         /* [HGM] Shogi promotions */
8452         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8453     }
8454
8455     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8456                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8457         // [HGM] superchess: take promotion piece out of holdings
8458         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8459         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8460             if(!--board[k][BOARD_WIDTH-2])
8461                 board[k][BOARD_WIDTH-1] = EmptySquare;
8462         } else {
8463             if(!--board[BOARD_HEIGHT-1-k][1])
8464                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8465         }
8466     }
8467
8468 }
8469
8470 /* Updates forwardMostMove */
8471 void
8472 MakeMove(fromX, fromY, toX, toY, promoChar)
8473      int fromX, fromY, toX, toY;
8474      int promoChar;
8475 {
8476 //    forwardMostMove++; // [HGM] bare: moved downstream
8477
8478     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8479         int timeLeft; static int lastLoadFlag=0; int king, piece;
8480         piece = boards[forwardMostMove][fromY][fromX];
8481         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8482         if(gameInfo.variant == VariantKnightmate)
8483             king += (int) WhiteUnicorn - (int) WhiteKing;
8484         if(forwardMostMove == 0) {
8485             if(blackPlaysFirst) 
8486                 fprintf(serverMoves, "%s;", second.tidy);
8487             fprintf(serverMoves, "%s;", first.tidy);
8488             if(!blackPlaysFirst) 
8489                 fprintf(serverMoves, "%s;", second.tidy);
8490         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8491         lastLoadFlag = loadFlag;
8492         // print base move
8493         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8494         // print castling suffix
8495         if( toY == fromY && piece == king ) {
8496             if(toX-fromX > 1)
8497                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8498             if(fromX-toX >1)
8499                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8500         }
8501         // e.p. suffix
8502         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8503              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8504              boards[forwardMostMove][toY][toX] == EmptySquare
8505              && fromX != toX )
8506                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8507         // promotion suffix
8508         if(promoChar != NULLCHAR)
8509                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8510         if(!loadFlag) {
8511             fprintf(serverMoves, "/%d/%d",
8512                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8513             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8514             else                      timeLeft = blackTimeRemaining/1000;
8515             fprintf(serverMoves, "/%d", timeLeft);
8516         }
8517         fflush(serverMoves);
8518     }
8519
8520     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8521       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8522                         0, 1);
8523       return;
8524     }
8525     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8526     if (commentList[forwardMostMove+1] != NULL) {
8527         free(commentList[forwardMostMove+1]);
8528         commentList[forwardMostMove+1] = NULL;
8529     }
8530     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8531     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8532     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8533     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8534     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8535     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8536     gameInfo.result = GameUnfinished;
8537     if (gameInfo.resultDetails != NULL) {
8538         free(gameInfo.resultDetails);
8539         gameInfo.resultDetails = NULL;
8540     }
8541     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8542                               moveList[forwardMostMove - 1]);
8543     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8544                              PosFlags(forwardMostMove - 1),
8545                              fromY, fromX, toY, toX, promoChar,
8546                              parseList[forwardMostMove - 1]);
8547     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8548       case MT_NONE:
8549       case MT_STALEMATE:
8550       default:
8551         break;
8552       case MT_CHECK:
8553         if(gameInfo.variant != VariantShogi)
8554             strcat(parseList[forwardMostMove - 1], "+");
8555         break;
8556       case MT_CHECKMATE:
8557       case MT_STAINMATE:
8558         strcat(parseList[forwardMostMove - 1], "#");
8559         break;
8560     }
8561     if (appData.debugMode) {
8562         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8563     }
8564
8565 }
8566
8567 /* Updates currentMove if not pausing */
8568 void
8569 ShowMove(fromX, fromY, toX, toY)
8570 {
8571     int instant = (gameMode == PlayFromGameFile) ?
8572         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8573     if(appData.noGUI) return;
8574     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8575         if (!instant) {
8576             if (forwardMostMove == currentMove + 1) {
8577                 AnimateMove(boards[forwardMostMove - 1],
8578                             fromX, fromY, toX, toY);
8579             }
8580             if (appData.highlightLastMove) {
8581                 SetHighlights(fromX, fromY, toX, toY);
8582             }
8583         }
8584         currentMove = forwardMostMove;
8585     }
8586
8587     if (instant) return;
8588
8589     DisplayMove(currentMove - 1);
8590     DrawPosition(FALSE, boards[currentMove]);
8591     DisplayBothClocks();
8592     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8593 }
8594
8595 void SendEgtPath(ChessProgramState *cps)
8596 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8597         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8598
8599         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8600
8601         while(*p) {
8602             char c, *q = name+1, *r, *s;
8603
8604             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8605             while(*p && *p != ',') *q++ = *p++;
8606             *q++ = ':'; *q = 0;
8607             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8608                 strcmp(name, ",nalimov:") == 0 ) {
8609                 // take nalimov path from the menu-changeable option first, if it is defined
8610                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8611                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8612             } else
8613             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8614                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8615                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8616                 s = r = StrStr(s, ":") + 1; // beginning of path info
8617                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8618                 c = *r; *r = 0;             // temporarily null-terminate path info
8619                     *--q = 0;               // strip of trailig ':' from name
8620                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8621                 *r = c;
8622                 SendToProgram(buf,cps);     // send egtbpath command for this format
8623             }
8624             if(*p == ',') p++; // read away comma to position for next format name
8625         }
8626 }
8627
8628 void
8629 InitChessProgram(cps, setup)
8630      ChessProgramState *cps;
8631      int setup; /* [HGM] needed to setup FRC opening position */
8632 {
8633     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8634     if (appData.noChessProgram) return;
8635     hintRequested = FALSE;
8636     bookRequested = FALSE;
8637
8638     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8639     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8640     if(cps->memSize) { /* [HGM] memory */
8641         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8642         SendToProgram(buf, cps);
8643     }
8644     SendEgtPath(cps); /* [HGM] EGT */
8645     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8646         sprintf(buf, "cores %d\n", appData.smpCores);
8647         SendToProgram(buf, cps);
8648     }
8649
8650     SendToProgram(cps->initString, cps);
8651     if (gameInfo.variant != VariantNormal &&
8652         gameInfo.variant != VariantLoadable
8653         /* [HGM] also send variant if board size non-standard */
8654         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8655                                             ) {
8656       char *v = VariantName(gameInfo.variant);
8657       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8658         /* [HGM] in protocol 1 we have to assume all variants valid */
8659         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8660         DisplayFatalError(buf, 0, 1);
8661         return;
8662       }
8663
8664       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8665       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8666       if( gameInfo.variant == VariantXiangqi )
8667            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8668       if( gameInfo.variant == VariantShogi )
8669            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8670       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8671            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8672       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8673                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8674            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8675       if( gameInfo.variant == VariantCourier )
8676            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8677       if( gameInfo.variant == VariantSuper )
8678            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8679       if( gameInfo.variant == VariantGreat )
8680            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8681
8682       if(overruled) {
8683            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8684                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8685            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8686            if(StrStr(cps->variants, b) == NULL) { 
8687                // specific sized variant not known, check if general sizing allowed
8688                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8689                    if(StrStr(cps->variants, "boardsize") == NULL) {
8690                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8691                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8692                        DisplayFatalError(buf, 0, 1);
8693                        return;
8694                    }
8695                    /* [HGM] here we really should compare with the maximum supported board size */
8696                }
8697            }
8698       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8699       sprintf(buf, "variant %s\n", b);
8700       SendToProgram(buf, cps);
8701     }
8702     currentlyInitializedVariant = gameInfo.variant;
8703
8704     /* [HGM] send opening position in FRC to first engine */
8705     if(setup) {
8706           SendToProgram("force\n", cps);
8707           SendBoard(cps, 0);
8708           /* engine is now in force mode! Set flag to wake it up after first move. */
8709           setboardSpoiledMachineBlack = 1;
8710     }
8711
8712     if (cps->sendICS) {
8713       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8714       SendToProgram(buf, cps);
8715     }
8716     cps->maybeThinking = FALSE;
8717     cps->offeredDraw = 0;
8718     if (!appData.icsActive) {
8719         SendTimeControl(cps, movesPerSession, timeControl,
8720                         timeIncrement, appData.searchDepth,
8721                         searchTime);
8722     }
8723     if (appData.showThinking 
8724         // [HGM] thinking: four options require thinking output to be sent
8725         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8726                                 ) {
8727         SendToProgram("post\n", cps);
8728     }
8729     SendToProgram("hard\n", cps);
8730     if (!appData.ponderNextMove) {
8731         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8732            it without being sure what state we are in first.  "hard"
8733            is not a toggle, so that one is OK.
8734          */
8735         SendToProgram("easy\n", cps);
8736     }
8737     if (cps->usePing) {
8738       sprintf(buf, "ping %d\n", ++cps->lastPing);
8739       SendToProgram(buf, cps);
8740     }
8741     cps->initDone = TRUE;
8742 }   
8743
8744
8745 void
8746 StartChessProgram(cps)
8747      ChessProgramState *cps;
8748 {
8749     char buf[MSG_SIZ];
8750     int err;
8751
8752     if (appData.noChessProgram) return;
8753     cps->initDone = FALSE;
8754
8755     if (strcmp(cps->host, "localhost") == 0) {
8756         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8757     } else if (*appData.remoteShell == NULLCHAR) {
8758         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8759     } else {
8760         if (*appData.remoteUser == NULLCHAR) {
8761           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8762                     cps->program);
8763         } else {
8764           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8765                     cps->host, appData.remoteUser, cps->program);
8766         }
8767         err = StartChildProcess(buf, "", &cps->pr);
8768     }
8769     
8770     if (err != 0) {
8771         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8772         DisplayFatalError(buf, err, 1);
8773         cps->pr = NoProc;
8774         cps->isr = NULL;
8775         return;
8776     }
8777     
8778     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8779     if (cps->protocolVersion > 1) {
8780       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8781       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8782       cps->comboCnt = 0;  //                and values of combo boxes
8783       SendToProgram(buf, cps);
8784     } else {
8785       SendToProgram("xboard\n", cps);
8786     }
8787 }
8788
8789
8790 void
8791 TwoMachinesEventIfReady P((void))
8792 {
8793   if (first.lastPing != first.lastPong) {
8794     DisplayMessage("", _("Waiting for first chess program"));
8795     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8796     return;
8797   }
8798   if (second.lastPing != second.lastPong) {
8799     DisplayMessage("", _("Waiting for second chess program"));
8800     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8801     return;
8802   }
8803   ThawUI();
8804   TwoMachinesEvent();
8805 }
8806
8807 void
8808 NextMatchGame P((void))
8809 {
8810     int index; /* [HGM] autoinc: step load index during match */
8811     Reset(FALSE, TRUE);
8812     if (*appData.loadGameFile != NULLCHAR) {
8813         index = appData.loadGameIndex;
8814         if(index < 0) { // [HGM] autoinc
8815             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8816             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8817         } 
8818         LoadGameFromFile(appData.loadGameFile,
8819                          index,
8820                          appData.loadGameFile, FALSE);
8821     } else if (*appData.loadPositionFile != NULLCHAR) {
8822         index = appData.loadPositionIndex;
8823         if(index < 0) { // [HGM] autoinc
8824             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8825             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8826         } 
8827         LoadPositionFromFile(appData.loadPositionFile,
8828                              index,
8829                              appData.loadPositionFile);
8830     }
8831     TwoMachinesEventIfReady();
8832 }
8833
8834 void UserAdjudicationEvent( int result )
8835 {
8836     ChessMove gameResult = GameIsDrawn;
8837
8838     if( result > 0 ) {
8839         gameResult = WhiteWins;
8840     }
8841     else if( result < 0 ) {
8842         gameResult = BlackWins;
8843     }
8844
8845     if( gameMode == TwoMachinesPlay ) {
8846         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8847     }
8848 }
8849
8850
8851 // [HGM] save: calculate checksum of game to make games easily identifiable
8852 int StringCheckSum(char *s)
8853 {
8854         int i = 0;
8855         if(s==NULL) return 0;
8856         while(*s) i = i*259 + *s++;
8857         return i;
8858 }
8859
8860 int GameCheckSum()
8861 {
8862         int i, sum=0;
8863         for(i=backwardMostMove; i<forwardMostMove; i++) {
8864                 sum += pvInfoList[i].depth;
8865                 sum += StringCheckSum(parseList[i]);
8866                 sum += StringCheckSum(commentList[i]);
8867                 sum *= 261;
8868         }
8869         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8870         return sum + StringCheckSum(commentList[i]);
8871 } // end of save patch
8872
8873 void
8874 GameEnds(result, resultDetails, whosays)
8875      ChessMove result;
8876      char *resultDetails;
8877      int whosays;
8878 {
8879     GameMode nextGameMode;
8880     int isIcsGame;
8881     char buf[MSG_SIZ];
8882
8883     if(endingGame) return; /* [HGM] crash: forbid recursion */
8884     endingGame = 1;
8885
8886     if (appData.debugMode) {
8887       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8888               result, resultDetails ? resultDetails : "(null)", whosays);
8889     }
8890
8891     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8892
8893     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8894         /* If we are playing on ICS, the server decides when the
8895            game is over, but the engine can offer to draw, claim 
8896            a draw, or resign. 
8897          */
8898 #if ZIPPY
8899         if (appData.zippyPlay && first.initDone) {
8900             if (result == GameIsDrawn) {
8901                 /* In case draw still needs to be claimed */
8902                 SendToICS(ics_prefix);
8903                 SendToICS("draw\n");
8904             } else if (StrCaseStr(resultDetails, "resign")) {
8905                 SendToICS(ics_prefix);
8906                 SendToICS("resign\n");
8907             }
8908         }
8909 #endif
8910         endingGame = 0; /* [HGM] crash */
8911         return;
8912     }
8913
8914     /* If we're loading the game from a file, stop */
8915     if (whosays == GE_FILE) {
8916       (void) StopLoadGameTimer();
8917       gameFileFP = NULL;
8918     }
8919
8920     /* Cancel draw offers */
8921     first.offeredDraw = second.offeredDraw = 0;
8922
8923     /* If this is an ICS game, only ICS can really say it's done;
8924        if not, anyone can. */
8925     isIcsGame = (gameMode == IcsPlayingWhite || 
8926                  gameMode == IcsPlayingBlack || 
8927                  gameMode == IcsObserving    || 
8928                  gameMode == IcsExamining);
8929
8930     if (!isIcsGame || whosays == GE_ICS) {
8931         /* OK -- not an ICS game, or ICS said it was done */
8932         StopClocks();
8933         if (!isIcsGame && !appData.noChessProgram) 
8934           SetUserThinkingEnables();
8935     
8936         /* [HGM] if a machine claims the game end we verify this claim */
8937         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8938             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8939                 char claimer;
8940                 ChessMove trueResult = (ChessMove) -1;
8941
8942                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8943                                             first.twoMachinesColor[0] :
8944                                             second.twoMachinesColor[0] ;
8945
8946                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8947                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8948                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8949                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8950                 } else
8951                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8952                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8953                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8954                 } else
8955                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8956                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8957                 }
8958
8959                 // now verify win claims, but not in drop games, as we don't understand those yet
8960                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8961                                                  || gameInfo.variant == VariantGreat) &&
8962                     (result == WhiteWins && claimer == 'w' ||
8963                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8964                       if (appData.debugMode) {
8965                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8966                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8967                       }
8968                       if(result != trueResult) {
8969                               sprintf(buf, "False win claim: '%s'", resultDetails);
8970                               result = claimer == 'w' ? BlackWins : WhiteWins;
8971                               resultDetails = buf;
8972                       }
8973                 } else
8974                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8975                     && (forwardMostMove <= backwardMostMove ||
8976                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8977                         (claimer=='b')==(forwardMostMove&1))
8978                                                                                   ) {
8979                       /* [HGM] verify: draws that were not flagged are false claims */
8980                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8981                       result = claimer == 'w' ? BlackWins : WhiteWins;
8982                       resultDetails = buf;
8983                 }
8984                 /* (Claiming a loss is accepted no questions asked!) */
8985             }
8986             /* [HGM] bare: don't allow bare King to win */
8987             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8988                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8989                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8990                && result != GameIsDrawn)
8991             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8992                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8993                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8994                         if(p >= 0 && p <= (int)WhiteKing) k++;
8995                 }
8996                 if (appData.debugMode) {
8997                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8998                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8999                 }
9000                 if(k <= 1) {
9001                         result = GameIsDrawn;
9002                         sprintf(buf, "%s but bare king", resultDetails);
9003                         resultDetails = buf;
9004                 }
9005             }
9006         }
9007
9008
9009         if(serverMoves != NULL && !loadFlag) { char c = '=';
9010             if(result==WhiteWins) c = '+';
9011             if(result==BlackWins) c = '-';
9012             if(resultDetails != NULL)
9013                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9014         }
9015         if (resultDetails != NULL) {
9016             gameInfo.result = result;
9017             gameInfo.resultDetails = StrSave(resultDetails);
9018
9019             /* display last move only if game was not loaded from file */
9020             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9021                 DisplayMove(currentMove - 1);
9022     
9023             if (forwardMostMove != 0) {
9024                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9025                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9026                                                                 ) {
9027                     if (*appData.saveGameFile != NULLCHAR) {
9028                         SaveGameToFile(appData.saveGameFile, TRUE);
9029                     } else if (appData.autoSaveGames) {
9030                         AutoSaveGame();
9031                     }
9032                     if (*appData.savePositionFile != NULLCHAR) {
9033                         SavePositionToFile(appData.savePositionFile);
9034                     }
9035                 }
9036             }
9037
9038             /* Tell program how game ended in case it is learning */
9039             /* [HGM] Moved this to after saving the PGN, just in case */
9040             /* engine died and we got here through time loss. In that */
9041             /* case we will get a fatal error writing the pipe, which */
9042             /* would otherwise lose us the PGN.                       */
9043             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9044             /* output during GameEnds should never be fatal anymore   */
9045             if (gameMode == MachinePlaysWhite ||
9046                 gameMode == MachinePlaysBlack ||
9047                 gameMode == TwoMachinesPlay ||
9048                 gameMode == IcsPlayingWhite ||
9049                 gameMode == IcsPlayingBlack ||
9050                 gameMode == BeginningOfGame) {
9051                 char buf[MSG_SIZ];
9052                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9053                         resultDetails);
9054                 if (first.pr != NoProc) {
9055                     SendToProgram(buf, &first);
9056                 }
9057                 if (second.pr != NoProc &&
9058                     gameMode == TwoMachinesPlay) {
9059                     SendToProgram(buf, &second);
9060                 }
9061             }
9062         }
9063
9064         if (appData.icsActive) {
9065             if (appData.quietPlay &&
9066                 (gameMode == IcsPlayingWhite ||
9067                  gameMode == IcsPlayingBlack)) {
9068                 SendToICS(ics_prefix);
9069                 SendToICS("set shout 1\n");
9070             }
9071             nextGameMode = IcsIdle;
9072             ics_user_moved = FALSE;
9073             /* clean up premove.  It's ugly when the game has ended and the
9074              * premove highlights are still on the board.
9075              */
9076             if (gotPremove) {
9077               gotPremove = FALSE;
9078               ClearPremoveHighlights();
9079               DrawPosition(FALSE, boards[currentMove]);
9080             }
9081             if (whosays == GE_ICS) {
9082                 switch (result) {
9083                 case WhiteWins:
9084                     if (gameMode == IcsPlayingWhite)
9085                         PlayIcsWinSound();
9086                     else if(gameMode == IcsPlayingBlack)
9087                         PlayIcsLossSound();
9088                     break;
9089                 case BlackWins:
9090                     if (gameMode == IcsPlayingBlack)
9091                         PlayIcsWinSound();
9092                     else if(gameMode == IcsPlayingWhite)
9093                         PlayIcsLossSound();
9094                     break;
9095                 case GameIsDrawn:
9096                     PlayIcsDrawSound();
9097                     break;
9098                 default:
9099                     PlayIcsUnfinishedSound();
9100                 }
9101             }
9102         } else if (gameMode == EditGame ||
9103                    gameMode == PlayFromGameFile || 
9104                    gameMode == AnalyzeMode || 
9105                    gameMode == AnalyzeFile) {
9106             nextGameMode = gameMode;
9107         } else {
9108             nextGameMode = EndOfGame;
9109         }
9110         pausing = FALSE;
9111         ModeHighlight();
9112     } else {
9113         nextGameMode = gameMode;
9114     }
9115
9116     if (appData.noChessProgram) {
9117         gameMode = nextGameMode;
9118         ModeHighlight();
9119         endingGame = 0; /* [HGM] crash */
9120         return;
9121     }
9122
9123     if (first.reuse) {
9124         /* Put first chess program into idle state */
9125         if (first.pr != NoProc &&
9126             (gameMode == MachinePlaysWhite ||
9127              gameMode == MachinePlaysBlack ||
9128              gameMode == TwoMachinesPlay ||
9129              gameMode == IcsPlayingWhite ||
9130              gameMode == IcsPlayingBlack ||
9131              gameMode == BeginningOfGame)) {
9132             SendToProgram("force\n", &first);
9133             if (first.usePing) {
9134               char buf[MSG_SIZ];
9135               sprintf(buf, "ping %d\n", ++first.lastPing);
9136               SendToProgram(buf, &first);
9137             }
9138         }
9139     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9140         /* Kill off first chess program */
9141         if (first.isr != NULL)
9142           RemoveInputSource(first.isr);
9143         first.isr = NULL;
9144     
9145         if (first.pr != NoProc) {
9146             ExitAnalyzeMode();
9147             DoSleep( appData.delayBeforeQuit );
9148             SendToProgram("quit\n", &first);
9149             DoSleep( appData.delayAfterQuit );
9150             DestroyChildProcess(first.pr, first.useSigterm);
9151         }
9152         first.pr = NoProc;
9153     }
9154     if (second.reuse) {
9155         /* Put second chess program into idle state */
9156         if (second.pr != NoProc &&
9157             gameMode == TwoMachinesPlay) {
9158             SendToProgram("force\n", &second);
9159             if (second.usePing) {
9160               char buf[MSG_SIZ];
9161               sprintf(buf, "ping %d\n", ++second.lastPing);
9162               SendToProgram(buf, &second);
9163             }
9164         }
9165     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9166         /* Kill off second chess program */
9167         if (second.isr != NULL)
9168           RemoveInputSource(second.isr);
9169         second.isr = NULL;
9170     
9171         if (second.pr != NoProc) {
9172             DoSleep( appData.delayBeforeQuit );
9173             SendToProgram("quit\n", &second);
9174             DoSleep( appData.delayAfterQuit );
9175             DestroyChildProcess(second.pr, second.useSigterm);
9176         }
9177         second.pr = NoProc;
9178     }
9179
9180     if (matchMode && gameMode == TwoMachinesPlay) {
9181         switch (result) {
9182         case WhiteWins:
9183           if (first.twoMachinesColor[0] == 'w') {
9184             first.matchWins++;
9185           } else {
9186             second.matchWins++;
9187           }
9188           break;
9189         case BlackWins:
9190           if (first.twoMachinesColor[0] == 'b') {
9191             first.matchWins++;
9192           } else {
9193             second.matchWins++;
9194           }
9195           break;
9196         default:
9197           break;
9198         }
9199         if (matchGame < appData.matchGames) {
9200             char *tmp;
9201             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9202                 tmp = first.twoMachinesColor;
9203                 first.twoMachinesColor = second.twoMachinesColor;
9204                 second.twoMachinesColor = tmp;
9205             }
9206             gameMode = nextGameMode;
9207             matchGame++;
9208             if(appData.matchPause>10000 || appData.matchPause<10)
9209                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9210             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9211             endingGame = 0; /* [HGM] crash */
9212             return;
9213         } else {
9214             char buf[MSG_SIZ];
9215             gameMode = nextGameMode;
9216             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9217                     first.tidy, second.tidy,
9218                     first.matchWins, second.matchWins,
9219                     appData.matchGames - (first.matchWins + second.matchWins));
9220             DisplayFatalError(buf, 0, 0);
9221         }
9222     }
9223     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9224         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9225       ExitAnalyzeMode();
9226     gameMode = nextGameMode;
9227     ModeHighlight();
9228     endingGame = 0;  /* [HGM] crash */
9229 }
9230
9231 /* Assumes program was just initialized (initString sent).
9232    Leaves program in force mode. */
9233 void
9234 FeedMovesToProgram(cps, upto) 
9235      ChessProgramState *cps;
9236      int upto;
9237 {
9238     int i;
9239     
9240     if (appData.debugMode)
9241       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9242               startedFromSetupPosition ? "position and " : "",
9243               backwardMostMove, upto, cps->which);
9244     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9245         // [HGM] variantswitch: make engine aware of new variant
9246         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9247                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9248         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9249         SendToProgram(buf, cps);
9250         currentlyInitializedVariant = gameInfo.variant;
9251     }
9252     SendToProgram("force\n", cps);
9253     if (startedFromSetupPosition) {
9254         SendBoard(cps, backwardMostMove);
9255     if (appData.debugMode) {
9256         fprintf(debugFP, "feedMoves\n");
9257     }
9258     }
9259     for (i = backwardMostMove; i < upto; i++) {
9260         SendMoveToProgram(i, cps);
9261     }
9262 }
9263
9264
9265 void
9266 ResurrectChessProgram()
9267 {
9268      /* The chess program may have exited.
9269         If so, restart it and feed it all the moves made so far. */
9270
9271     if (appData.noChessProgram || first.pr != NoProc) return;
9272     
9273     StartChessProgram(&first);
9274     InitChessProgram(&first, FALSE);
9275     FeedMovesToProgram(&first, currentMove);
9276
9277     if (!first.sendTime) {
9278         /* can't tell gnuchess what its clock should read,
9279            so we bow to its notion. */
9280         ResetClocks();
9281         timeRemaining[0][currentMove] = whiteTimeRemaining;
9282         timeRemaining[1][currentMove] = blackTimeRemaining;
9283     }
9284
9285     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9286                 appData.icsEngineAnalyze) && first.analysisSupport) {
9287       SendToProgram("analyze\n", &first);
9288       first.analyzing = TRUE;
9289     }
9290 }
9291
9292 /*
9293  * Button procedures
9294  */
9295 void
9296 Reset(redraw, init)
9297      int redraw, init;
9298 {
9299     int i;
9300
9301     if (appData.debugMode) {
9302         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9303                 redraw, init, gameMode);
9304     }
9305     CleanupTail(); // [HGM] vari: delete any stored variations
9306     pausing = pauseExamInvalid = FALSE;
9307     startedFromSetupPosition = blackPlaysFirst = FALSE;
9308     firstMove = TRUE;
9309     whiteFlag = blackFlag = FALSE;
9310     userOfferedDraw = FALSE;
9311     hintRequested = bookRequested = FALSE;
9312     first.maybeThinking = FALSE;
9313     second.maybeThinking = FALSE;
9314     first.bookSuspend = FALSE; // [HGM] book
9315     second.bookSuspend = FALSE;
9316     thinkOutput[0] = NULLCHAR;
9317     lastHint[0] = NULLCHAR;
9318     ClearGameInfo(&gameInfo);
9319     gameInfo.variant = StringToVariant(appData.variant);
9320     ics_user_moved = ics_clock_paused = FALSE;
9321     ics_getting_history = H_FALSE;
9322     ics_gamenum = -1;
9323     white_holding[0] = black_holding[0] = NULLCHAR;
9324     ClearProgramStats();
9325     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9326     
9327     ResetFrontEnd();
9328     ClearHighlights();
9329     flipView = appData.flipView;
9330     ClearPremoveHighlights();
9331     gotPremove = FALSE;
9332     alarmSounded = FALSE;
9333
9334     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9335     if(appData.serverMovesName != NULL) {
9336         /* [HGM] prepare to make moves file for broadcasting */
9337         clock_t t = clock();
9338         if(serverMoves != NULL) fclose(serverMoves);
9339         serverMoves = fopen(appData.serverMovesName, "r");
9340         if(serverMoves != NULL) {
9341             fclose(serverMoves);
9342             /* delay 15 sec before overwriting, so all clients can see end */
9343             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9344         }
9345         serverMoves = fopen(appData.serverMovesName, "w");
9346     }
9347
9348     ExitAnalyzeMode();
9349     gameMode = BeginningOfGame;
9350     ModeHighlight();
9351     if(appData.icsActive) gameInfo.variant = VariantNormal;
9352     currentMove = forwardMostMove = backwardMostMove = 0;
9353     InitPosition(redraw);
9354     for (i = 0; i < MAX_MOVES; i++) {
9355         if (commentList[i] != NULL) {
9356             free(commentList[i]);
9357             commentList[i] = NULL;
9358         }
9359     }
9360     ResetClocks();
9361     timeRemaining[0][0] = whiteTimeRemaining;
9362     timeRemaining[1][0] = blackTimeRemaining;
9363     if (first.pr == NULL) {
9364         StartChessProgram(&first);
9365     }
9366     if (init) {
9367             InitChessProgram(&first, startedFromSetupPosition);
9368     }
9369     DisplayTitle("");
9370     DisplayMessage("", "");
9371     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9372     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9373 }
9374
9375 void
9376 AutoPlayGameLoop()
9377 {
9378     for (;;) {
9379         if (!AutoPlayOneMove())
9380           return;
9381         if (matchMode || appData.timeDelay == 0)
9382           continue;
9383         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9384           return;
9385         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9386         break;
9387     }
9388 }
9389
9390
9391 int
9392 AutoPlayOneMove()
9393 {
9394     int fromX, fromY, toX, toY;
9395
9396     if (appData.debugMode) {
9397       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9398     }
9399
9400     if (gameMode != PlayFromGameFile)
9401       return FALSE;
9402
9403     if (currentMove >= forwardMostMove) {
9404       gameMode = EditGame;
9405       ModeHighlight();
9406
9407       /* [AS] Clear current move marker at the end of a game */
9408       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9409
9410       return FALSE;
9411     }
9412     
9413     toX = moveList[currentMove][2] - AAA;
9414     toY = moveList[currentMove][3] - ONE;
9415
9416     if (moveList[currentMove][1] == '@') {
9417         if (appData.highlightLastMove) {
9418             SetHighlights(-1, -1, toX, toY);
9419         }
9420     } else {
9421         fromX = moveList[currentMove][0] - AAA;
9422         fromY = moveList[currentMove][1] - ONE;
9423
9424         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9425
9426         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9427
9428         if (appData.highlightLastMove) {
9429             SetHighlights(fromX, fromY, toX, toY);
9430         }
9431     }
9432     DisplayMove(currentMove);
9433     SendMoveToProgram(currentMove++, &first);
9434     DisplayBothClocks();
9435     DrawPosition(FALSE, boards[currentMove]);
9436     // [HGM] PV info: always display, routine tests if empty
9437     DisplayComment(currentMove - 1, commentList[currentMove]);
9438     return TRUE;
9439 }
9440
9441
9442 int
9443 LoadGameOneMove(readAhead)
9444      ChessMove readAhead;
9445 {
9446     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9447     char promoChar = NULLCHAR;
9448     ChessMove moveType;
9449     char move[MSG_SIZ];
9450     char *p, *q;
9451     
9452     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9453         gameMode != AnalyzeMode && gameMode != Training) {
9454         gameFileFP = NULL;
9455         return FALSE;
9456     }
9457     
9458     yyboardindex = forwardMostMove;
9459     if (readAhead != (ChessMove)0) {
9460       moveType = readAhead;
9461     } else {
9462       if (gameFileFP == NULL)
9463           return FALSE;
9464       moveType = (ChessMove) yylex();
9465     }
9466     
9467     done = FALSE;
9468     switch (moveType) {
9469       case Comment:
9470         if (appData.debugMode) 
9471           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9472         p = yy_text;
9473
9474         /* append the comment but don't display it */
9475         AppendComment(currentMove, p, FALSE);
9476         return TRUE;
9477
9478       case WhiteCapturesEnPassant:
9479       case BlackCapturesEnPassant:
9480       case WhitePromotionChancellor:
9481       case BlackPromotionChancellor:
9482       case WhitePromotionArchbishop:
9483       case BlackPromotionArchbishop:
9484       case WhitePromotionCentaur:
9485       case BlackPromotionCentaur:
9486       case WhitePromotionQueen:
9487       case BlackPromotionQueen:
9488       case WhitePromotionRook:
9489       case BlackPromotionRook:
9490       case WhitePromotionBishop:
9491       case BlackPromotionBishop:
9492       case WhitePromotionKnight:
9493       case BlackPromotionKnight:
9494       case WhitePromotionKing:
9495       case BlackPromotionKing:
9496       case NormalMove:
9497       case WhiteKingSideCastle:
9498       case WhiteQueenSideCastle:
9499       case BlackKingSideCastle:
9500       case BlackQueenSideCastle:
9501       case WhiteKingSideCastleWild:
9502       case WhiteQueenSideCastleWild:
9503       case BlackKingSideCastleWild:
9504       case BlackQueenSideCastleWild:
9505       /* PUSH Fabien */
9506       case WhiteHSideCastleFR:
9507       case WhiteASideCastleFR:
9508       case BlackHSideCastleFR:
9509       case BlackASideCastleFR:
9510       /* POP Fabien */
9511         if (appData.debugMode)
9512           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9513         fromX = currentMoveString[0] - AAA;
9514         fromY = currentMoveString[1] - ONE;
9515         toX = currentMoveString[2] - AAA;
9516         toY = currentMoveString[3] - ONE;
9517         promoChar = currentMoveString[4];
9518         break;
9519
9520       case WhiteDrop:
9521       case BlackDrop:
9522         if (appData.debugMode)
9523           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9524         fromX = moveType == WhiteDrop ?
9525           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9526         (int) CharToPiece(ToLower(currentMoveString[0]));
9527         fromY = DROP_RANK;
9528         toX = currentMoveString[2] - AAA;
9529         toY = currentMoveString[3] - ONE;
9530         break;
9531
9532       case WhiteWins:
9533       case BlackWins:
9534       case GameIsDrawn:
9535       case GameUnfinished:
9536         if (appData.debugMode)
9537           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9538         p = strchr(yy_text, '{');
9539         if (p == NULL) p = strchr(yy_text, '(');
9540         if (p == NULL) {
9541             p = yy_text;
9542             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9543         } else {
9544             q = strchr(p, *p == '{' ? '}' : ')');
9545             if (q != NULL) *q = NULLCHAR;
9546             p++;
9547         }
9548         GameEnds(moveType, p, GE_FILE);
9549         done = TRUE;
9550         if (cmailMsgLoaded) {
9551             ClearHighlights();
9552             flipView = WhiteOnMove(currentMove);
9553             if (moveType == GameUnfinished) flipView = !flipView;
9554             if (appData.debugMode)
9555               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9556         }
9557         break;
9558
9559       case (ChessMove) 0:       /* end of file */
9560         if (appData.debugMode)
9561           fprintf(debugFP, "Parser hit end of file\n");
9562         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9563           case MT_NONE:
9564           case MT_CHECK:
9565             break;
9566           case MT_CHECKMATE:
9567           case MT_STAINMATE:
9568             if (WhiteOnMove(currentMove)) {
9569                 GameEnds(BlackWins, "Black mates", GE_FILE);
9570             } else {
9571                 GameEnds(WhiteWins, "White mates", GE_FILE);
9572             }
9573             break;
9574           case MT_STALEMATE:
9575             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9576             break;
9577         }
9578         done = TRUE;
9579         break;
9580
9581       case MoveNumberOne:
9582         if (lastLoadGameStart == GNUChessGame) {
9583             /* GNUChessGames have numbers, but they aren't move numbers */
9584             if (appData.debugMode)
9585               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9586                       yy_text, (int) moveType);
9587             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9588         }
9589         /* else fall thru */
9590
9591       case XBoardGame:
9592       case GNUChessGame:
9593       case PGNTag:
9594         /* Reached start of next game in file */
9595         if (appData.debugMode)
9596           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9597         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9598           case MT_NONE:
9599           case MT_CHECK:
9600             break;
9601           case MT_CHECKMATE:
9602           case MT_STAINMATE:
9603             if (WhiteOnMove(currentMove)) {
9604                 GameEnds(BlackWins, "Black mates", GE_FILE);
9605             } else {
9606                 GameEnds(WhiteWins, "White mates", GE_FILE);
9607             }
9608             break;
9609           case MT_STALEMATE:
9610             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9611             break;
9612         }
9613         done = TRUE;
9614         break;
9615
9616       case PositionDiagram:     /* should not happen; ignore */
9617       case ElapsedTime:         /* ignore */
9618       case NAG:                 /* ignore */
9619         if (appData.debugMode)
9620           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9621                   yy_text, (int) moveType);
9622         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9623
9624       case IllegalMove:
9625         if (appData.testLegality) {
9626             if (appData.debugMode)
9627               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9628             sprintf(move, _("Illegal move: %d.%s%s"),
9629                     (forwardMostMove / 2) + 1,
9630                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9631             DisplayError(move, 0);
9632             done = TRUE;
9633         } else {
9634             if (appData.debugMode)
9635               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9636                       yy_text, currentMoveString);
9637             fromX = currentMoveString[0] - AAA;
9638             fromY = currentMoveString[1] - ONE;
9639             toX = currentMoveString[2] - AAA;
9640             toY = currentMoveString[3] - ONE;
9641             promoChar = currentMoveString[4];
9642         }
9643         break;
9644
9645       case AmbiguousMove:
9646         if (appData.debugMode)
9647           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9648         sprintf(move, _("Ambiguous move: %d.%s%s"),
9649                 (forwardMostMove / 2) + 1,
9650                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9651         DisplayError(move, 0);
9652         done = TRUE;
9653         break;
9654
9655       default:
9656       case ImpossibleMove:
9657         if (appData.debugMode)
9658           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9659         sprintf(move, _("Illegal move: %d.%s%s"),
9660                 (forwardMostMove / 2) + 1,
9661                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9662         DisplayError(move, 0);
9663         done = TRUE;
9664         break;
9665     }
9666
9667     if (done) {
9668         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9669             DrawPosition(FALSE, boards[currentMove]);
9670             DisplayBothClocks();
9671             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9672               DisplayComment(currentMove - 1, commentList[currentMove]);
9673         }
9674         (void) StopLoadGameTimer();
9675         gameFileFP = NULL;
9676         cmailOldMove = forwardMostMove;
9677         return FALSE;
9678     } else {
9679         /* currentMoveString is set as a side-effect of yylex */
9680         strcat(currentMoveString, "\n");
9681         strcpy(moveList[forwardMostMove], currentMoveString);
9682         
9683         thinkOutput[0] = NULLCHAR;
9684         MakeMove(fromX, fromY, toX, toY, promoChar);
9685         currentMove = forwardMostMove;
9686         return TRUE;
9687     }
9688 }
9689
9690 /* Load the nth game from the given file */
9691 int
9692 LoadGameFromFile(filename, n, title, useList)
9693      char *filename;
9694      int n;
9695      char *title;
9696      /*Boolean*/ int useList;
9697 {
9698     FILE *f;
9699     char buf[MSG_SIZ];
9700
9701     if (strcmp(filename, "-") == 0) {
9702         f = stdin;
9703         title = "stdin";
9704     } else {
9705         f = fopen(filename, "rb");
9706         if (f == NULL) {
9707           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9708             DisplayError(buf, errno);
9709             return FALSE;
9710         }
9711     }
9712     if (fseek(f, 0, 0) == -1) {
9713         /* f is not seekable; probably a pipe */
9714         useList = FALSE;
9715     }
9716     if (useList && n == 0) {
9717         int error = GameListBuild(f);
9718         if (error) {
9719             DisplayError(_("Cannot build game list"), error);
9720         } else if (!ListEmpty(&gameList) &&
9721                    ((ListGame *) gameList.tailPred)->number > 1) {
9722             GameListPopUp(f, title);
9723             return TRUE;
9724         }
9725         GameListDestroy();
9726         n = 1;
9727     }
9728     if (n == 0) n = 1;
9729     return LoadGame(f, n, title, FALSE);
9730 }
9731
9732
9733 void
9734 MakeRegisteredMove()
9735 {
9736     int fromX, fromY, toX, toY;
9737     char promoChar;
9738     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9739         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9740           case CMAIL_MOVE:
9741           case CMAIL_DRAW:
9742             if (appData.debugMode)
9743               fprintf(debugFP, "Restoring %s for game %d\n",
9744                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9745     
9746             thinkOutput[0] = NULLCHAR;
9747             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9748             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9749             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9750             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9751             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9752             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9753             MakeMove(fromX, fromY, toX, toY, promoChar);
9754             ShowMove(fromX, fromY, toX, toY);
9755               
9756             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9757               case MT_NONE:
9758               case MT_CHECK:
9759                 break;
9760                 
9761               case MT_CHECKMATE:
9762               case MT_STAINMATE:
9763                 if (WhiteOnMove(currentMove)) {
9764                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9765                 } else {
9766                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9767                 }
9768                 break;
9769                 
9770               case MT_STALEMATE:
9771                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9772                 break;
9773             }
9774
9775             break;
9776             
9777           case CMAIL_RESIGN:
9778             if (WhiteOnMove(currentMove)) {
9779                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9780             } else {
9781                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9782             }
9783             break;
9784             
9785           case CMAIL_ACCEPT:
9786             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9787             break;
9788               
9789           default:
9790             break;
9791         }
9792     }
9793
9794     return;
9795 }
9796
9797 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9798 int
9799 CmailLoadGame(f, gameNumber, title, useList)
9800      FILE *f;
9801      int gameNumber;
9802      char *title;
9803      int useList;
9804 {
9805     int retVal;
9806
9807     if (gameNumber > nCmailGames) {
9808         DisplayError(_("No more games in this message"), 0);
9809         return FALSE;
9810     }
9811     if (f == lastLoadGameFP) {
9812         int offset = gameNumber - lastLoadGameNumber;
9813         if (offset == 0) {
9814             cmailMsg[0] = NULLCHAR;
9815             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9816                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9817                 nCmailMovesRegistered--;
9818             }
9819             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9820             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9821                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9822             }
9823         } else {
9824             if (! RegisterMove()) return FALSE;
9825         }
9826     }
9827
9828     retVal = LoadGame(f, gameNumber, title, useList);
9829
9830     /* Make move registered during previous look at this game, if any */
9831     MakeRegisteredMove();
9832
9833     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9834         commentList[currentMove]
9835           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9836         DisplayComment(currentMove - 1, commentList[currentMove]);
9837     }
9838
9839     return retVal;
9840 }
9841
9842 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9843 int
9844 ReloadGame(offset)
9845      int offset;
9846 {
9847     int gameNumber = lastLoadGameNumber + offset;
9848     if (lastLoadGameFP == NULL) {
9849         DisplayError(_("No game has been loaded yet"), 0);
9850         return FALSE;
9851     }
9852     if (gameNumber <= 0) {
9853         DisplayError(_("Can't back up any further"), 0);
9854         return FALSE;
9855     }
9856     if (cmailMsgLoaded) {
9857         return CmailLoadGame(lastLoadGameFP, gameNumber,
9858                              lastLoadGameTitle, lastLoadGameUseList);
9859     } else {
9860         return LoadGame(lastLoadGameFP, gameNumber,
9861                         lastLoadGameTitle, lastLoadGameUseList);
9862     }
9863 }
9864
9865
9866
9867 /* Load the nth game from open file f */
9868 int
9869 LoadGame(f, gameNumber, title, useList)
9870      FILE *f;
9871      int gameNumber;
9872      char *title;
9873      int useList;
9874 {
9875     ChessMove cm;
9876     char buf[MSG_SIZ];
9877     int gn = gameNumber;
9878     ListGame *lg = NULL;
9879     int numPGNTags = 0;
9880     int err;
9881     GameMode oldGameMode;
9882     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9883
9884     if (appData.debugMode) 
9885         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9886
9887     if (gameMode == Training )
9888         SetTrainingModeOff();
9889
9890     oldGameMode = gameMode;
9891     if (gameMode != BeginningOfGame) {
9892       Reset(FALSE, TRUE);
9893     }
9894
9895     gameFileFP = f;
9896     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9897         fclose(lastLoadGameFP);
9898     }
9899
9900     if (useList) {
9901         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9902         
9903         if (lg) {
9904             fseek(f, lg->offset, 0);
9905             GameListHighlight(gameNumber);
9906             gn = 1;
9907         }
9908         else {
9909             DisplayError(_("Game number out of range"), 0);
9910             return FALSE;
9911         }
9912     } else {
9913         GameListDestroy();
9914         if (fseek(f, 0, 0) == -1) {
9915             if (f == lastLoadGameFP ?
9916                 gameNumber == lastLoadGameNumber + 1 :
9917                 gameNumber == 1) {
9918                 gn = 1;
9919             } else {
9920                 DisplayError(_("Can't seek on game file"), 0);
9921                 return FALSE;
9922             }
9923         }
9924     }
9925     lastLoadGameFP = f;
9926     lastLoadGameNumber = gameNumber;
9927     strcpy(lastLoadGameTitle, title);
9928     lastLoadGameUseList = useList;
9929
9930     yynewfile(f);
9931
9932     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9933       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9934                 lg->gameInfo.black);
9935             DisplayTitle(buf);
9936     } else if (*title != NULLCHAR) {
9937         if (gameNumber > 1) {
9938             sprintf(buf, "%s %d", title, gameNumber);
9939             DisplayTitle(buf);
9940         } else {
9941             DisplayTitle(title);
9942         }
9943     }
9944
9945     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9946         gameMode = PlayFromGameFile;
9947         ModeHighlight();
9948     }
9949
9950     currentMove = forwardMostMove = backwardMostMove = 0;
9951     CopyBoard(boards[0], initialPosition);
9952     StopClocks();
9953
9954     /*
9955      * Skip the first gn-1 games in the file.
9956      * Also skip over anything that precedes an identifiable 
9957      * start of game marker, to avoid being confused by 
9958      * garbage at the start of the file.  Currently 
9959      * recognized start of game markers are the move number "1",
9960      * the pattern "gnuchess .* game", the pattern
9961      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9962      * A game that starts with one of the latter two patterns
9963      * will also have a move number 1, possibly
9964      * following a position diagram.
9965      * 5-4-02: Let's try being more lenient and allowing a game to
9966      * start with an unnumbered move.  Does that break anything?
9967      */
9968     cm = lastLoadGameStart = (ChessMove) 0;
9969     while (gn > 0) {
9970         yyboardindex = forwardMostMove;
9971         cm = (ChessMove) yylex();
9972         switch (cm) {
9973           case (ChessMove) 0:
9974             if (cmailMsgLoaded) {
9975                 nCmailGames = CMAIL_MAX_GAMES - gn;
9976             } else {
9977                 Reset(TRUE, TRUE);
9978                 DisplayError(_("Game not found in file"), 0);
9979             }
9980             return FALSE;
9981
9982           case GNUChessGame:
9983           case XBoardGame:
9984             gn--;
9985             lastLoadGameStart = cm;
9986             break;
9987             
9988           case MoveNumberOne:
9989             switch (lastLoadGameStart) {
9990               case GNUChessGame:
9991               case XBoardGame:
9992               case PGNTag:
9993                 break;
9994               case MoveNumberOne:
9995               case (ChessMove) 0:
9996                 gn--;           /* count this game */
9997                 lastLoadGameStart = cm;
9998                 break;
9999               default:
10000                 /* impossible */
10001                 break;
10002             }
10003             break;
10004
10005           case PGNTag:
10006             switch (lastLoadGameStart) {
10007               case GNUChessGame:
10008               case PGNTag:
10009               case MoveNumberOne:
10010               case (ChessMove) 0:
10011                 gn--;           /* count this game */
10012                 lastLoadGameStart = cm;
10013                 break;
10014               case XBoardGame:
10015                 lastLoadGameStart = cm; /* game counted already */
10016                 break;
10017               default:
10018                 /* impossible */
10019                 break;
10020             }
10021             if (gn > 0) {
10022                 do {
10023                     yyboardindex = forwardMostMove;
10024                     cm = (ChessMove) yylex();
10025                 } while (cm == PGNTag || cm == Comment);
10026             }
10027             break;
10028
10029           case WhiteWins:
10030           case BlackWins:
10031           case GameIsDrawn:
10032             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10033                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10034                     != CMAIL_OLD_RESULT) {
10035                     nCmailResults ++ ;
10036                     cmailResult[  CMAIL_MAX_GAMES
10037                                 - gn - 1] = CMAIL_OLD_RESULT;
10038                 }
10039             }
10040             break;
10041
10042           case NormalMove:
10043             /* Only a NormalMove can be at the start of a game
10044              * without a position diagram. */
10045             if (lastLoadGameStart == (ChessMove) 0) {
10046               gn--;
10047               lastLoadGameStart = MoveNumberOne;
10048             }
10049             break;
10050
10051           default:
10052             break;
10053         }
10054     }
10055     
10056     if (appData.debugMode)
10057       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10058
10059     if (cm == XBoardGame) {
10060         /* Skip any header junk before position diagram and/or move 1 */
10061         for (;;) {
10062             yyboardindex = forwardMostMove;
10063             cm = (ChessMove) yylex();
10064
10065             if (cm == (ChessMove) 0 ||
10066                 cm == GNUChessGame || cm == XBoardGame) {
10067                 /* Empty game; pretend end-of-file and handle later */
10068                 cm = (ChessMove) 0;
10069                 break;
10070             }
10071
10072             if (cm == MoveNumberOne || cm == PositionDiagram ||
10073                 cm == PGNTag || cm == Comment)
10074               break;
10075         }
10076     } else if (cm == GNUChessGame) {
10077         if (gameInfo.event != NULL) {
10078             free(gameInfo.event);
10079         }
10080         gameInfo.event = StrSave(yy_text);
10081     }   
10082
10083     startedFromSetupPosition = FALSE;
10084     while (cm == PGNTag) {
10085         if (appData.debugMode) 
10086           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10087         err = ParsePGNTag(yy_text, &gameInfo);
10088         if (!err) numPGNTags++;
10089
10090         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10091         if(gameInfo.variant != oldVariant) {
10092             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10093             InitPosition(TRUE);
10094             oldVariant = gameInfo.variant;
10095             if (appData.debugMode) 
10096               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10097         }
10098
10099
10100         if (gameInfo.fen != NULL) {
10101           Board initial_position;
10102           startedFromSetupPosition = TRUE;
10103           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10104             Reset(TRUE, TRUE);
10105             DisplayError(_("Bad FEN position in file"), 0);
10106             return FALSE;
10107           }
10108           CopyBoard(boards[0], initial_position);
10109           if (blackPlaysFirst) {
10110             currentMove = forwardMostMove = backwardMostMove = 1;
10111             CopyBoard(boards[1], initial_position);
10112             strcpy(moveList[0], "");
10113             strcpy(parseList[0], "");
10114             timeRemaining[0][1] = whiteTimeRemaining;
10115             timeRemaining[1][1] = blackTimeRemaining;
10116             if (commentList[0] != NULL) {
10117               commentList[1] = commentList[0];
10118               commentList[0] = NULL;
10119             }
10120           } else {
10121             currentMove = forwardMostMove = backwardMostMove = 0;
10122           }
10123           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10124           {   int i;
10125               initialRulePlies = FENrulePlies;
10126               for( i=0; i< nrCastlingRights; i++ )
10127                   initialRights[i] = initial_position[CASTLING][i];
10128           }
10129           yyboardindex = forwardMostMove;
10130           free(gameInfo.fen);
10131           gameInfo.fen = NULL;
10132         }
10133
10134         yyboardindex = forwardMostMove;
10135         cm = (ChessMove) yylex();
10136
10137         /* Handle comments interspersed among the tags */
10138         while (cm == Comment) {
10139             char *p;
10140             if (appData.debugMode) 
10141               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10142             p = yy_text;
10143             AppendComment(currentMove, p, FALSE);
10144             yyboardindex = forwardMostMove;
10145             cm = (ChessMove) yylex();
10146         }
10147     }
10148
10149     /* don't rely on existence of Event tag since if game was
10150      * pasted from clipboard the Event tag may not exist
10151      */
10152     if (numPGNTags > 0){
10153         char *tags;
10154         if (gameInfo.variant == VariantNormal) {
10155           gameInfo.variant = StringToVariant(gameInfo.event);
10156         }
10157         if (!matchMode) {
10158           if( appData.autoDisplayTags ) {
10159             tags = PGNTags(&gameInfo);
10160             TagsPopUp(tags, CmailMsg());
10161             free(tags);
10162           }
10163         }
10164     } else {
10165         /* Make something up, but don't display it now */
10166         SetGameInfo();
10167         TagsPopDown();
10168     }
10169
10170     if (cm == PositionDiagram) {
10171         int i, j;
10172         char *p;
10173         Board initial_position;
10174
10175         if (appData.debugMode)
10176           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10177
10178         if (!startedFromSetupPosition) {
10179             p = yy_text;
10180             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10181               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10182                 switch (*p) {
10183                   case '[':
10184                   case '-':
10185                   case ' ':
10186                   case '\t':
10187                   case '\n':
10188                   case '\r':
10189                     break;
10190                   default:
10191                     initial_position[i][j++] = CharToPiece(*p);
10192                     break;
10193                 }
10194             while (*p == ' ' || *p == '\t' ||
10195                    *p == '\n' || *p == '\r') p++;
10196         
10197             if (strncmp(p, "black", strlen("black"))==0)
10198               blackPlaysFirst = TRUE;
10199             else
10200               blackPlaysFirst = FALSE;
10201             startedFromSetupPosition = TRUE;
10202         
10203             CopyBoard(boards[0], initial_position);
10204             if (blackPlaysFirst) {
10205                 currentMove = forwardMostMove = backwardMostMove = 1;
10206                 CopyBoard(boards[1], initial_position);
10207                 strcpy(moveList[0], "");
10208                 strcpy(parseList[0], "");
10209                 timeRemaining[0][1] = whiteTimeRemaining;
10210                 timeRemaining[1][1] = blackTimeRemaining;
10211                 if (commentList[0] != NULL) {
10212                     commentList[1] = commentList[0];
10213                     commentList[0] = NULL;
10214                 }
10215             } else {
10216                 currentMove = forwardMostMove = backwardMostMove = 0;
10217             }
10218         }
10219         yyboardindex = forwardMostMove;
10220         cm = (ChessMove) yylex();
10221     }
10222
10223     if (first.pr == NoProc) {
10224         StartChessProgram(&first);
10225     }
10226     InitChessProgram(&first, FALSE);
10227     SendToProgram("force\n", &first);
10228     if (startedFromSetupPosition) {
10229         SendBoard(&first, forwardMostMove);
10230     if (appData.debugMode) {
10231         fprintf(debugFP, "Load Game\n");
10232     }
10233         DisplayBothClocks();
10234     }      
10235
10236     /* [HGM] server: flag to write setup moves in broadcast file as one */
10237     loadFlag = appData.suppressLoadMoves;
10238
10239     while (cm == Comment) {
10240         char *p;
10241         if (appData.debugMode) 
10242           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10243         p = yy_text;
10244         AppendComment(currentMove, p, FALSE);
10245         yyboardindex = forwardMostMove;
10246         cm = (ChessMove) yylex();
10247     }
10248
10249     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10250         cm == WhiteWins || cm == BlackWins ||
10251         cm == GameIsDrawn || cm == GameUnfinished) {
10252         DisplayMessage("", _("No moves in game"));
10253         if (cmailMsgLoaded) {
10254             if (appData.debugMode)
10255               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10256             ClearHighlights();
10257             flipView = FALSE;
10258         }
10259         DrawPosition(FALSE, boards[currentMove]);
10260         DisplayBothClocks();
10261         gameMode = EditGame;
10262         ModeHighlight();
10263         gameFileFP = NULL;
10264         cmailOldMove = 0;
10265         return TRUE;
10266     }
10267
10268     // [HGM] PV info: routine tests if comment empty
10269     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10270         DisplayComment(currentMove - 1, commentList[currentMove]);
10271     }
10272     if (!matchMode && appData.timeDelay != 0) 
10273       DrawPosition(FALSE, boards[currentMove]);
10274
10275     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10276       programStats.ok_to_send = 1;
10277     }
10278
10279     /* if the first token after the PGN tags is a move
10280      * and not move number 1, retrieve it from the parser 
10281      */
10282     if (cm != MoveNumberOne)
10283         LoadGameOneMove(cm);
10284
10285     /* load the remaining moves from the file */
10286     while (LoadGameOneMove((ChessMove)0)) {
10287       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10288       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10289     }
10290
10291     /* rewind to the start of the game */
10292     currentMove = backwardMostMove;
10293
10294     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10295
10296     if (oldGameMode == AnalyzeFile ||
10297         oldGameMode == AnalyzeMode) {
10298       AnalyzeFileEvent();
10299     }
10300
10301     if (matchMode || appData.timeDelay == 0) {
10302       ToEndEvent();
10303       gameMode = EditGame;
10304       ModeHighlight();
10305     } else if (appData.timeDelay > 0) {
10306       AutoPlayGameLoop();
10307     }
10308
10309     if (appData.debugMode) 
10310         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10311
10312     loadFlag = 0; /* [HGM] true game starts */
10313     return TRUE;
10314 }
10315
10316 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10317 int
10318 ReloadPosition(offset)
10319      int offset;
10320 {
10321     int positionNumber = lastLoadPositionNumber + offset;
10322     if (lastLoadPositionFP == NULL) {
10323         DisplayError(_("No position has been loaded yet"), 0);
10324         return FALSE;
10325     }
10326     if (positionNumber <= 0) {
10327         DisplayError(_("Can't back up any further"), 0);
10328         return FALSE;
10329     }
10330     return LoadPosition(lastLoadPositionFP, positionNumber,
10331                         lastLoadPositionTitle);
10332 }
10333
10334 /* Load the nth position from the given file */
10335 int
10336 LoadPositionFromFile(filename, n, title)
10337      char *filename;
10338      int n;
10339      char *title;
10340 {
10341     FILE *f;
10342     char buf[MSG_SIZ];
10343
10344     if (strcmp(filename, "-") == 0) {
10345         return LoadPosition(stdin, n, "stdin");
10346     } else {
10347         f = fopen(filename, "rb");
10348         if (f == NULL) {
10349             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10350             DisplayError(buf, errno);
10351             return FALSE;
10352         } else {
10353             return LoadPosition(f, n, title);
10354         }
10355     }
10356 }
10357
10358 /* Load the nth position from the given open file, and close it */
10359 int
10360 LoadPosition(f, positionNumber, title)
10361      FILE *f;
10362      int positionNumber;
10363      char *title;
10364 {
10365     char *p, line[MSG_SIZ];
10366     Board initial_position;
10367     int i, j, fenMode, pn;
10368     
10369     if (gameMode == Training )
10370         SetTrainingModeOff();
10371
10372     if (gameMode != BeginningOfGame) {
10373         Reset(FALSE, TRUE);
10374     }
10375     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10376         fclose(lastLoadPositionFP);
10377     }
10378     if (positionNumber == 0) positionNumber = 1;
10379     lastLoadPositionFP = f;
10380     lastLoadPositionNumber = positionNumber;
10381     strcpy(lastLoadPositionTitle, title);
10382     if (first.pr == NoProc) {
10383       StartChessProgram(&first);
10384       InitChessProgram(&first, FALSE);
10385     }    
10386     pn = positionNumber;
10387     if (positionNumber < 0) {
10388         /* Negative position number means to seek to that byte offset */
10389         if (fseek(f, -positionNumber, 0) == -1) {
10390             DisplayError(_("Can't seek on position file"), 0);
10391             return FALSE;
10392         };
10393         pn = 1;
10394     } else {
10395         if (fseek(f, 0, 0) == -1) {
10396             if (f == lastLoadPositionFP ?
10397                 positionNumber == lastLoadPositionNumber + 1 :
10398                 positionNumber == 1) {
10399                 pn = 1;
10400             } else {
10401                 DisplayError(_("Can't seek on position file"), 0);
10402                 return FALSE;
10403             }
10404         }
10405     }
10406     /* See if this file is FEN or old-style xboard */
10407     if (fgets(line, MSG_SIZ, f) == NULL) {
10408         DisplayError(_("Position not found in file"), 0);
10409         return FALSE;
10410     }
10411     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10412     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10413
10414     if (pn >= 2) {
10415         if (fenMode || line[0] == '#') pn--;
10416         while (pn > 0) {
10417             /* skip positions before number pn */
10418             if (fgets(line, MSG_SIZ, f) == NULL) {
10419                 Reset(TRUE, TRUE);
10420                 DisplayError(_("Position not found in file"), 0);
10421                 return FALSE;
10422             }
10423             if (fenMode || line[0] == '#') pn--;
10424         }
10425     }
10426
10427     if (fenMode) {
10428         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10429             DisplayError(_("Bad FEN position in file"), 0);
10430             return FALSE;
10431         }
10432     } else {
10433         (void) fgets(line, MSG_SIZ, f);
10434         (void) fgets(line, MSG_SIZ, f);
10435     
10436         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10437             (void) fgets(line, MSG_SIZ, f);
10438             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10439                 if (*p == ' ')
10440                   continue;
10441                 initial_position[i][j++] = CharToPiece(*p);
10442             }
10443         }
10444     
10445         blackPlaysFirst = FALSE;
10446         if (!feof(f)) {
10447             (void) fgets(line, MSG_SIZ, f);
10448             if (strncmp(line, "black", strlen("black"))==0)
10449               blackPlaysFirst = TRUE;
10450         }
10451     }
10452     startedFromSetupPosition = TRUE;
10453     
10454     SendToProgram("force\n", &first);
10455     CopyBoard(boards[0], initial_position);
10456     if (blackPlaysFirst) {
10457         currentMove = forwardMostMove = backwardMostMove = 1;
10458         strcpy(moveList[0], "");
10459         strcpy(parseList[0], "");
10460         CopyBoard(boards[1], initial_position);
10461         DisplayMessage("", _("Black to play"));
10462     } else {
10463         currentMove = forwardMostMove = backwardMostMove = 0;
10464         DisplayMessage("", _("White to play"));
10465     }
10466     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10467     SendBoard(&first, forwardMostMove);
10468     if (appData.debugMode) {
10469 int i, j;
10470   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10471   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10472         fprintf(debugFP, "Load Position\n");
10473     }
10474
10475     if (positionNumber > 1) {
10476         sprintf(line, "%s %d", title, positionNumber);
10477         DisplayTitle(line);
10478     } else {
10479         DisplayTitle(title);
10480     }
10481     gameMode = EditGame;
10482     ModeHighlight();
10483     ResetClocks();
10484     timeRemaining[0][1] = whiteTimeRemaining;
10485     timeRemaining[1][1] = blackTimeRemaining;
10486     DrawPosition(FALSE, boards[currentMove]);
10487    
10488     return TRUE;
10489 }
10490
10491
10492 void
10493 CopyPlayerNameIntoFileName(dest, src)
10494      char **dest, *src;
10495 {
10496     while (*src != NULLCHAR && *src != ',') {
10497         if (*src == ' ') {
10498             *(*dest)++ = '_';
10499             src++;
10500         } else {
10501             *(*dest)++ = *src++;
10502         }
10503     }
10504 }
10505
10506 char *DefaultFileName(ext)
10507      char *ext;
10508 {
10509     static char def[MSG_SIZ];
10510     char *p;
10511
10512     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10513         p = def;
10514         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10515         *p++ = '-';
10516         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10517         *p++ = '.';
10518         strcpy(p, ext);
10519     } else {
10520         def[0] = NULLCHAR;
10521     }
10522     return def;
10523 }
10524
10525 /* Save the current game to the given file */
10526 int
10527 SaveGameToFile(filename, append)
10528      char *filename;
10529      int append;
10530 {
10531     FILE *f;
10532     char buf[MSG_SIZ];
10533
10534     if (strcmp(filename, "-") == 0) {
10535         return SaveGame(stdout, 0, NULL);
10536     } else {
10537         f = fopen(filename, append ? "a" : "w");
10538         if (f == NULL) {
10539             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10540             DisplayError(buf, errno);
10541             return FALSE;
10542         } else {
10543             return SaveGame(f, 0, NULL);
10544         }
10545     }
10546 }
10547
10548 char *
10549 SavePart(str)
10550      char *str;
10551 {
10552     static char buf[MSG_SIZ];
10553     char *p;
10554     
10555     p = strchr(str, ' ');
10556     if (p == NULL) return str;
10557     strncpy(buf, str, p - str);
10558     buf[p - str] = NULLCHAR;
10559     return buf;
10560 }
10561
10562 #define PGN_MAX_LINE 75
10563
10564 #define PGN_SIDE_WHITE  0
10565 #define PGN_SIDE_BLACK  1
10566
10567 /* [AS] */
10568 static int FindFirstMoveOutOfBook( int side )
10569 {
10570     int result = -1;
10571
10572     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10573         int index = backwardMostMove;
10574         int has_book_hit = 0;
10575
10576         if( (index % 2) != side ) {
10577             index++;
10578         }
10579
10580         while( index < forwardMostMove ) {
10581             /* Check to see if engine is in book */
10582             int depth = pvInfoList[index].depth;
10583             int score = pvInfoList[index].score;
10584             int in_book = 0;
10585
10586             if( depth <= 2 ) {
10587                 in_book = 1;
10588             }
10589             else if( score == 0 && depth == 63 ) {
10590                 in_book = 1; /* Zappa */
10591             }
10592             else if( score == 2 && depth == 99 ) {
10593                 in_book = 1; /* Abrok */
10594             }
10595
10596             has_book_hit += in_book;
10597
10598             if( ! in_book ) {
10599                 result = index;
10600
10601                 break;
10602             }
10603
10604             index += 2;
10605         }
10606     }
10607
10608     return result;
10609 }
10610
10611 /* [AS] */
10612 void GetOutOfBookInfo( char * buf )
10613 {
10614     int oob[2];
10615     int i;
10616     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10617
10618     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10619     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10620
10621     *buf = '\0';
10622
10623     if( oob[0] >= 0 || oob[1] >= 0 ) {
10624         for( i=0; i<2; i++ ) {
10625             int idx = oob[i];
10626
10627             if( idx >= 0 ) {
10628                 if( i > 0 && oob[0] >= 0 ) {
10629                     strcat( buf, "   " );
10630                 }
10631
10632                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10633                 sprintf( buf+strlen(buf), "%s%.2f", 
10634                     pvInfoList[idx].score >= 0 ? "+" : "",
10635                     pvInfoList[idx].score / 100.0 );
10636             }
10637         }
10638     }
10639 }
10640
10641 /* Save game in PGN style and close the file */
10642 int
10643 SaveGamePGN(f)
10644      FILE *f;
10645 {
10646     int i, offset, linelen, newblock;
10647     time_t tm;
10648 //    char *movetext;
10649     char numtext[32];
10650     int movelen, numlen, blank;
10651     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10652
10653     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10654     
10655     tm = time((time_t *) NULL);
10656     
10657     PrintPGNTags(f, &gameInfo);
10658     
10659     if (backwardMostMove > 0 || startedFromSetupPosition) {
10660         char *fen = PositionToFEN(backwardMostMove, NULL);
10661         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10662         fprintf(f, "\n{--------------\n");
10663         PrintPosition(f, backwardMostMove);
10664         fprintf(f, "--------------}\n");
10665         free(fen);
10666     }
10667     else {
10668         /* [AS] Out of book annotation */
10669         if( appData.saveOutOfBookInfo ) {
10670             char buf[64];
10671
10672             GetOutOfBookInfo( buf );
10673
10674             if( buf[0] != '\0' ) {
10675                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10676             }
10677         }
10678
10679         fprintf(f, "\n");
10680     }
10681
10682     i = backwardMostMove;
10683     linelen = 0;
10684     newblock = TRUE;
10685
10686     while (i < forwardMostMove) {
10687         /* Print comments preceding this move */
10688         if (commentList[i] != NULL) {
10689             if (linelen > 0) fprintf(f, "\n");
10690             fprintf(f, "%s", commentList[i]);
10691             linelen = 0;
10692             newblock = TRUE;
10693         }
10694
10695         /* Format move number */
10696         if ((i % 2) == 0) {
10697             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10698         } else {
10699             if (newblock) {
10700                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10701             } else {
10702                 numtext[0] = NULLCHAR;
10703             }
10704         }
10705         numlen = strlen(numtext);
10706         newblock = FALSE;
10707
10708         /* Print move number */
10709         blank = linelen > 0 && numlen > 0;
10710         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10711             fprintf(f, "\n");
10712             linelen = 0;
10713             blank = 0;
10714         }
10715         if (blank) {
10716             fprintf(f, " ");
10717             linelen++;
10718         }
10719         fprintf(f, "%s", numtext);
10720         linelen += numlen;
10721
10722         /* Get move */
10723         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10724         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10725
10726         /* Print move */
10727         blank = linelen > 0 && movelen > 0;
10728         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10729             fprintf(f, "\n");
10730             linelen = 0;
10731             blank = 0;
10732         }
10733         if (blank) {
10734             fprintf(f, " ");
10735             linelen++;
10736         }
10737         fprintf(f, "%s", move_buffer);
10738         linelen += movelen;
10739
10740         /* [AS] Add PV info if present */
10741         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10742             /* [HGM] add time */
10743             char buf[MSG_SIZ]; int seconds;
10744
10745             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10746
10747             if( seconds <= 0) buf[0] = 0; else
10748             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10749                 seconds = (seconds + 4)/10; // round to full seconds
10750                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10751                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10752             }
10753
10754             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10755                 pvInfoList[i].score >= 0 ? "+" : "",
10756                 pvInfoList[i].score / 100.0,
10757                 pvInfoList[i].depth,
10758                 buf );
10759
10760             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10761
10762             /* Print score/depth */
10763             blank = linelen > 0 && movelen > 0;
10764             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10765                 fprintf(f, "\n");
10766                 linelen = 0;
10767                 blank = 0;
10768             }
10769             if (blank) {
10770                 fprintf(f, " ");
10771                 linelen++;
10772             }
10773             fprintf(f, "%s", move_buffer);
10774             linelen += movelen;
10775         }
10776
10777         i++;
10778     }
10779     
10780     /* Start a new line */
10781     if (linelen > 0) fprintf(f, "\n");
10782
10783     /* Print comments after last move */
10784     if (commentList[i] != NULL) {
10785         fprintf(f, "%s\n", commentList[i]);
10786     }
10787
10788     /* Print result */
10789     if (gameInfo.resultDetails != NULL &&
10790         gameInfo.resultDetails[0] != NULLCHAR) {
10791         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10792                 PGNResult(gameInfo.result));
10793     } else {
10794         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10795     }
10796
10797     fclose(f);
10798     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10799     return TRUE;
10800 }
10801
10802 /* Save game in old style and close the file */
10803 int
10804 SaveGameOldStyle(f)
10805      FILE *f;
10806 {
10807     int i, offset;
10808     time_t tm;
10809     
10810     tm = time((time_t *) NULL);
10811     
10812     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10813     PrintOpponents(f);
10814     
10815     if (backwardMostMove > 0 || startedFromSetupPosition) {
10816         fprintf(f, "\n[--------------\n");
10817         PrintPosition(f, backwardMostMove);
10818         fprintf(f, "--------------]\n");
10819     } else {
10820         fprintf(f, "\n");
10821     }
10822
10823     i = backwardMostMove;
10824     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10825
10826     while (i < forwardMostMove) {
10827         if (commentList[i] != NULL) {
10828             fprintf(f, "[%s]\n", commentList[i]);
10829         }
10830
10831         if ((i % 2) == 1) {
10832             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10833             i++;
10834         } else {
10835             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10836             i++;
10837             if (commentList[i] != NULL) {
10838                 fprintf(f, "\n");
10839                 continue;
10840             }
10841             if (i >= forwardMostMove) {
10842                 fprintf(f, "\n");
10843                 break;
10844             }
10845             fprintf(f, "%s\n", parseList[i]);
10846             i++;
10847         }
10848     }
10849     
10850     if (commentList[i] != NULL) {
10851         fprintf(f, "[%s]\n", commentList[i]);
10852     }
10853
10854     /* This isn't really the old style, but it's close enough */
10855     if (gameInfo.resultDetails != NULL &&
10856         gameInfo.resultDetails[0] != NULLCHAR) {
10857         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10858                 gameInfo.resultDetails);
10859     } else {
10860         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10861     }
10862
10863     fclose(f);
10864     return TRUE;
10865 }
10866
10867 /* Save the current game to open file f and close the file */
10868 int
10869 SaveGame(f, dummy, dummy2)
10870      FILE *f;
10871      int dummy;
10872      char *dummy2;
10873 {
10874     if (gameMode == EditPosition) EditPositionDone(TRUE);
10875     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10876     if (appData.oldSaveStyle)
10877       return SaveGameOldStyle(f);
10878     else
10879       return SaveGamePGN(f);
10880 }
10881
10882 /* Save the current position to the given file */
10883 int
10884 SavePositionToFile(filename)
10885      char *filename;
10886 {
10887     FILE *f;
10888     char buf[MSG_SIZ];
10889
10890     if (strcmp(filename, "-") == 0) {
10891         return SavePosition(stdout, 0, NULL);
10892     } else {
10893         f = fopen(filename, "a");
10894         if (f == NULL) {
10895             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10896             DisplayError(buf, errno);
10897             return FALSE;
10898         } else {
10899             SavePosition(f, 0, NULL);
10900             return TRUE;
10901         }
10902     }
10903 }
10904
10905 /* Save the current position to the given open file and close the file */
10906 int
10907 SavePosition(f, dummy, dummy2)
10908      FILE *f;
10909      int dummy;
10910      char *dummy2;
10911 {
10912     time_t tm;
10913     char *fen;
10914     
10915     if (gameMode == EditPosition) EditPositionDone(TRUE);
10916     if (appData.oldSaveStyle) {
10917         tm = time((time_t *) NULL);
10918     
10919         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10920         PrintOpponents(f);
10921         fprintf(f, "[--------------\n");
10922         PrintPosition(f, currentMove);
10923         fprintf(f, "--------------]\n");
10924     } else {
10925         fen = PositionToFEN(currentMove, NULL);
10926         fprintf(f, "%s\n", fen);
10927         free(fen);
10928     }
10929     fclose(f);
10930     return TRUE;
10931 }
10932
10933 void
10934 ReloadCmailMsgEvent(unregister)
10935      int unregister;
10936 {
10937 #if !WIN32
10938     static char *inFilename = NULL;
10939     static char *outFilename;
10940     int i;
10941     struct stat inbuf, outbuf;
10942     int status;
10943     
10944     /* Any registered moves are unregistered if unregister is set, */
10945     /* i.e. invoked by the signal handler */
10946     if (unregister) {
10947         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10948             cmailMoveRegistered[i] = FALSE;
10949             if (cmailCommentList[i] != NULL) {
10950                 free(cmailCommentList[i]);
10951                 cmailCommentList[i] = NULL;
10952             }
10953         }
10954         nCmailMovesRegistered = 0;
10955     }
10956
10957     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10958         cmailResult[i] = CMAIL_NOT_RESULT;
10959     }
10960     nCmailResults = 0;
10961
10962     if (inFilename == NULL) {
10963         /* Because the filenames are static they only get malloced once  */
10964         /* and they never get freed                                      */
10965         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10966         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10967
10968         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10969         sprintf(outFilename, "%s.out", appData.cmailGameName);
10970     }
10971     
10972     status = stat(outFilename, &outbuf);
10973     if (status < 0) {
10974         cmailMailedMove = FALSE;
10975     } else {
10976         status = stat(inFilename, &inbuf);
10977         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10978     }
10979     
10980     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10981        counts the games, notes how each one terminated, etc.
10982        
10983        It would be nice to remove this kludge and instead gather all
10984        the information while building the game list.  (And to keep it
10985        in the game list nodes instead of having a bunch of fixed-size
10986        parallel arrays.)  Note this will require getting each game's
10987        termination from the PGN tags, as the game list builder does
10988        not process the game moves.  --mann
10989        */
10990     cmailMsgLoaded = TRUE;
10991     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10992     
10993     /* Load first game in the file or popup game menu */
10994     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10995
10996 #endif /* !WIN32 */
10997     return;
10998 }
10999
11000 int
11001 RegisterMove()
11002 {
11003     FILE *f;
11004     char string[MSG_SIZ];
11005
11006     if (   cmailMailedMove
11007         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11008         return TRUE;            /* Allow free viewing  */
11009     }
11010
11011     /* Unregister move to ensure that we don't leave RegisterMove        */
11012     /* with the move registered when the conditions for registering no   */
11013     /* longer hold                                                       */
11014     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11015         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11016         nCmailMovesRegistered --;
11017
11018         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11019           {
11020               free(cmailCommentList[lastLoadGameNumber - 1]);
11021               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11022           }
11023     }
11024
11025     if (cmailOldMove == -1) {
11026         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11027         return FALSE;
11028     }
11029
11030     if (currentMove > cmailOldMove + 1) {
11031         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11032         return FALSE;
11033     }
11034
11035     if (currentMove < cmailOldMove) {
11036         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11037         return FALSE;
11038     }
11039
11040     if (forwardMostMove > currentMove) {
11041         /* Silently truncate extra moves */
11042         TruncateGame();
11043     }
11044
11045     if (   (currentMove == cmailOldMove + 1)
11046         || (   (currentMove == cmailOldMove)
11047             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11048                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11049         if (gameInfo.result != GameUnfinished) {
11050             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11051         }
11052
11053         if (commentList[currentMove] != NULL) {
11054             cmailCommentList[lastLoadGameNumber - 1]
11055               = StrSave(commentList[currentMove]);
11056         }
11057         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11058
11059         if (appData.debugMode)
11060           fprintf(debugFP, "Saving %s for game %d\n",
11061                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11062
11063         sprintf(string,
11064                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11065         
11066         f = fopen(string, "w");
11067         if (appData.oldSaveStyle) {
11068             SaveGameOldStyle(f); /* also closes the file */
11069             
11070             sprintf(string, "%s.pos.out", appData.cmailGameName);
11071             f = fopen(string, "w");
11072             SavePosition(f, 0, NULL); /* also closes the file */
11073         } else {
11074             fprintf(f, "{--------------\n");
11075             PrintPosition(f, currentMove);
11076             fprintf(f, "--------------}\n\n");
11077             
11078             SaveGame(f, 0, NULL); /* also closes the file*/
11079         }
11080         
11081         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11082         nCmailMovesRegistered ++;
11083     } else if (nCmailGames == 1) {
11084         DisplayError(_("You have not made a move yet"), 0);
11085         return FALSE;
11086     }
11087
11088     return TRUE;
11089 }
11090
11091 void
11092 MailMoveEvent()
11093 {
11094 #if !WIN32
11095     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11096     FILE *commandOutput;
11097     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11098     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11099     int nBuffers;
11100     int i;
11101     int archived;
11102     char *arcDir;
11103
11104     if (! cmailMsgLoaded) {
11105         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11106         return;
11107     }
11108
11109     if (nCmailGames == nCmailResults) {
11110         DisplayError(_("No unfinished games"), 0);
11111         return;
11112     }
11113
11114 #if CMAIL_PROHIBIT_REMAIL
11115     if (cmailMailedMove) {
11116         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);
11117         DisplayError(msg, 0);
11118         return;
11119     }
11120 #endif
11121
11122     if (! (cmailMailedMove || RegisterMove())) return;
11123     
11124     if (   cmailMailedMove
11125         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11126         sprintf(string, partCommandString,
11127                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11128         commandOutput = popen(string, "r");
11129
11130         if (commandOutput == NULL) {
11131             DisplayError(_("Failed to invoke cmail"), 0);
11132         } else {
11133             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11134                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11135             }
11136             if (nBuffers > 1) {
11137                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11138                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11139                 nBytes = MSG_SIZ - 1;
11140             } else {
11141                 (void) memcpy(msg, buffer, nBytes);
11142             }
11143             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11144
11145             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11146                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11147
11148                 archived = TRUE;
11149                 for (i = 0; i < nCmailGames; i ++) {
11150                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11151                         archived = FALSE;
11152                     }
11153                 }
11154                 if (   archived
11155                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11156                         != NULL)) {
11157                     sprintf(buffer, "%s/%s.%s.archive",
11158                             arcDir,
11159                             appData.cmailGameName,
11160                             gameInfo.date);
11161                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11162                     cmailMsgLoaded = FALSE;
11163                 }
11164             }
11165
11166             DisplayInformation(msg);
11167             pclose(commandOutput);
11168         }
11169     } else {
11170         if ((*cmailMsg) != '\0') {
11171             DisplayInformation(cmailMsg);
11172         }
11173     }
11174
11175     return;
11176 #endif /* !WIN32 */
11177 }
11178
11179 char *
11180 CmailMsg()
11181 {
11182 #if WIN32
11183     return NULL;
11184 #else
11185     int  prependComma = 0;
11186     char number[5];
11187     char string[MSG_SIZ];       /* Space for game-list */
11188     int  i;
11189     
11190     if (!cmailMsgLoaded) return "";
11191
11192     if (cmailMailedMove) {
11193         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11194     } else {
11195         /* Create a list of games left */
11196         sprintf(string, "[");
11197         for (i = 0; i < nCmailGames; i ++) {
11198             if (! (   cmailMoveRegistered[i]
11199                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11200                 if (prependComma) {
11201                     sprintf(number, ",%d", i + 1);
11202                 } else {
11203                     sprintf(number, "%d", i + 1);
11204                     prependComma = 1;
11205                 }
11206                 
11207                 strcat(string, number);
11208             }
11209         }
11210         strcat(string, "]");
11211
11212         if (nCmailMovesRegistered + nCmailResults == 0) {
11213             switch (nCmailGames) {
11214               case 1:
11215                 sprintf(cmailMsg,
11216                         _("Still need to make move for game\n"));
11217                 break;
11218                 
11219               case 2:
11220                 sprintf(cmailMsg,
11221                         _("Still need to make moves for both games\n"));
11222                 break;
11223                 
11224               default:
11225                 sprintf(cmailMsg,
11226                         _("Still need to make moves for all %d games\n"),
11227                         nCmailGames);
11228                 break;
11229             }
11230         } else {
11231             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11232               case 1:
11233                 sprintf(cmailMsg,
11234                         _("Still need to make a move for game %s\n"),
11235                         string);
11236                 break;
11237                 
11238               case 0:
11239                 if (nCmailResults == nCmailGames) {
11240                     sprintf(cmailMsg, _("No unfinished games\n"));
11241                 } else {
11242                     sprintf(cmailMsg, _("Ready to send mail\n"));
11243                 }
11244                 break;
11245                 
11246               default:
11247                 sprintf(cmailMsg,
11248                         _("Still need to make moves for games %s\n"),
11249                         string);
11250             }
11251         }
11252     }
11253     return cmailMsg;
11254 #endif /* WIN32 */
11255 }
11256
11257 void
11258 ResetGameEvent()
11259 {
11260     if (gameMode == Training)
11261       SetTrainingModeOff();
11262
11263     Reset(TRUE, TRUE);
11264     cmailMsgLoaded = FALSE;
11265     if (appData.icsActive) {
11266       SendToICS(ics_prefix);
11267       SendToICS("refresh\n");
11268     }
11269 }
11270
11271 void
11272 ExitEvent(status)
11273      int status;
11274 {
11275     exiting++;
11276     if (exiting > 2) {
11277       /* Give up on clean exit */
11278       exit(status);
11279     }
11280     if (exiting > 1) {
11281       /* Keep trying for clean exit */
11282       return;
11283     }
11284
11285     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11286
11287     if (telnetISR != NULL) {
11288       RemoveInputSource(telnetISR);
11289     }
11290     if (icsPR != NoProc) {
11291       DestroyChildProcess(icsPR, TRUE);
11292     }
11293
11294     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11295     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11296
11297     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11298     /* make sure this other one finishes before killing it!                  */
11299     if(endingGame) { int count = 0;
11300         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11301         while(endingGame && count++ < 10) DoSleep(1);
11302         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11303     }
11304
11305     /* Kill off chess programs */
11306     if (first.pr != NoProc) {
11307         ExitAnalyzeMode();
11308         
11309         DoSleep( appData.delayBeforeQuit );
11310         SendToProgram("quit\n", &first);
11311         DoSleep( appData.delayAfterQuit );
11312         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11313     }
11314     if (second.pr != NoProc) {
11315         DoSleep( appData.delayBeforeQuit );
11316         SendToProgram("quit\n", &second);
11317         DoSleep( appData.delayAfterQuit );
11318         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11319     }
11320     if (first.isr != NULL) {
11321         RemoveInputSource(first.isr);
11322     }
11323     if (second.isr != NULL) {
11324         RemoveInputSource(second.isr);
11325     }
11326
11327     ShutDownFrontEnd();
11328     exit(status);
11329 }
11330
11331 void
11332 PauseEvent()
11333 {
11334     if (appData.debugMode)
11335         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11336     if (pausing) {
11337         pausing = FALSE;
11338         ModeHighlight();
11339         if (gameMode == MachinePlaysWhite ||
11340             gameMode == MachinePlaysBlack) {
11341             StartClocks();
11342         } else {
11343             DisplayBothClocks();
11344         }
11345         if (gameMode == PlayFromGameFile) {
11346             if (appData.timeDelay >= 0) 
11347                 AutoPlayGameLoop();
11348         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11349             Reset(FALSE, TRUE);
11350             SendToICS(ics_prefix);
11351             SendToICS("refresh\n");
11352         } else if (currentMove < forwardMostMove) {
11353             ForwardInner(forwardMostMove);
11354         }
11355         pauseExamInvalid = FALSE;
11356     } else {
11357         switch (gameMode) {
11358           default:
11359             return;
11360           case IcsExamining:
11361             pauseExamForwardMostMove = forwardMostMove;
11362             pauseExamInvalid = FALSE;
11363             /* fall through */
11364           case IcsObserving:
11365           case IcsPlayingWhite:
11366           case IcsPlayingBlack:
11367             pausing = TRUE;
11368             ModeHighlight();
11369             return;
11370           case PlayFromGameFile:
11371             (void) StopLoadGameTimer();
11372             pausing = TRUE;
11373             ModeHighlight();
11374             break;
11375           case BeginningOfGame:
11376             if (appData.icsActive) return;
11377             /* else fall through */
11378           case MachinePlaysWhite:
11379           case MachinePlaysBlack:
11380           case TwoMachinesPlay:
11381             if (forwardMostMove == 0)
11382               return;           /* don't pause if no one has moved */
11383             if ((gameMode == MachinePlaysWhite &&
11384                  !WhiteOnMove(forwardMostMove)) ||
11385                 (gameMode == MachinePlaysBlack &&
11386                  WhiteOnMove(forwardMostMove))) {
11387                 StopClocks();
11388             }
11389             pausing = TRUE;
11390             ModeHighlight();
11391             break;
11392         }
11393     }
11394 }
11395
11396 void
11397 EditCommentEvent()
11398 {
11399     char title[MSG_SIZ];
11400
11401     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11402         strcpy(title, _("Edit comment"));
11403     } else {
11404         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11405                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11406                 parseList[currentMove - 1]);
11407     }
11408
11409     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11410 }
11411
11412
11413 void
11414 EditTagsEvent()
11415 {
11416     char *tags = PGNTags(&gameInfo);
11417     EditTagsPopUp(tags);
11418     free(tags);
11419 }
11420
11421 void
11422 AnalyzeModeEvent()
11423 {
11424     if (appData.noChessProgram || gameMode == AnalyzeMode)
11425       return;
11426
11427     if (gameMode != AnalyzeFile) {
11428         if (!appData.icsEngineAnalyze) {
11429                EditGameEvent();
11430                if (gameMode != EditGame) return;
11431         }
11432         ResurrectChessProgram();
11433         SendToProgram("analyze\n", &first);
11434         first.analyzing = TRUE;
11435         /*first.maybeThinking = TRUE;*/
11436         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11437         EngineOutputPopUp();
11438     }
11439     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11440     pausing = FALSE;
11441     ModeHighlight();
11442     SetGameInfo();
11443
11444     StartAnalysisClock();
11445     GetTimeMark(&lastNodeCountTime);
11446     lastNodeCount = 0;
11447 }
11448
11449 void
11450 AnalyzeFileEvent()
11451 {
11452     if (appData.noChessProgram || gameMode == AnalyzeFile)
11453       return;
11454
11455     if (gameMode != AnalyzeMode) {
11456         EditGameEvent();
11457         if (gameMode != EditGame) return;
11458         ResurrectChessProgram();
11459         SendToProgram("analyze\n", &first);
11460         first.analyzing = TRUE;
11461         /*first.maybeThinking = TRUE;*/
11462         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11463         EngineOutputPopUp();
11464     }
11465     gameMode = AnalyzeFile;
11466     pausing = FALSE;
11467     ModeHighlight();
11468     SetGameInfo();
11469
11470     StartAnalysisClock();
11471     GetTimeMark(&lastNodeCountTime);
11472     lastNodeCount = 0;
11473 }
11474
11475 void
11476 MachineWhiteEvent()
11477 {
11478     char buf[MSG_SIZ];
11479     char *bookHit = NULL;
11480
11481     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11482       return;
11483
11484
11485     if (gameMode == PlayFromGameFile || 
11486         gameMode == TwoMachinesPlay  || 
11487         gameMode == Training         || 
11488         gameMode == AnalyzeMode      || 
11489         gameMode == EndOfGame)
11490         EditGameEvent();
11491
11492     if (gameMode == EditPosition) 
11493         EditPositionDone(TRUE);
11494
11495     if (!WhiteOnMove(currentMove)) {
11496         DisplayError(_("It is not White's turn"), 0);
11497         return;
11498     }
11499   
11500     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11501       ExitAnalyzeMode();
11502
11503     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11504         gameMode == AnalyzeFile)
11505         TruncateGame();
11506
11507     ResurrectChessProgram();    /* in case it isn't running */
11508     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11509         gameMode = MachinePlaysWhite;
11510         ResetClocks();
11511     } else
11512     gameMode = MachinePlaysWhite;
11513     pausing = FALSE;
11514     ModeHighlight();
11515     SetGameInfo();
11516     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11517     DisplayTitle(buf);
11518     if (first.sendName) {
11519       sprintf(buf, "name %s\n", gameInfo.black);
11520       SendToProgram(buf, &first);
11521     }
11522     if (first.sendTime) {
11523       if (first.useColors) {
11524         SendToProgram("black\n", &first); /*gnu kludge*/
11525       }
11526       SendTimeRemaining(&first, TRUE);
11527     }
11528     if (first.useColors) {
11529       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11530     }
11531     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11532     SetMachineThinkingEnables();
11533     first.maybeThinking = TRUE;
11534     StartClocks();
11535     firstMove = FALSE;
11536
11537     if (appData.autoFlipView && !flipView) {
11538       flipView = !flipView;
11539       DrawPosition(FALSE, NULL);
11540       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11541     }
11542
11543     if(bookHit) { // [HGM] book: simulate book reply
11544         static char bookMove[MSG_SIZ]; // a bit generous?
11545
11546         programStats.nodes = programStats.depth = programStats.time = 
11547         programStats.score = programStats.got_only_move = 0;
11548         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11549
11550         strcpy(bookMove, "move ");
11551         strcat(bookMove, bookHit);
11552         HandleMachineMove(bookMove, &first);
11553     }
11554 }
11555
11556 void
11557 MachineBlackEvent()
11558 {
11559     char buf[MSG_SIZ];
11560    char *bookHit = NULL;
11561
11562     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11563         return;
11564
11565
11566     if (gameMode == PlayFromGameFile || 
11567         gameMode == TwoMachinesPlay  || 
11568         gameMode == Training         || 
11569         gameMode == AnalyzeMode      || 
11570         gameMode == EndOfGame)
11571         EditGameEvent();
11572
11573     if (gameMode == EditPosition) 
11574         EditPositionDone(TRUE);
11575
11576     if (WhiteOnMove(currentMove)) {
11577         DisplayError(_("It is not Black's turn"), 0);
11578         return;
11579     }
11580     
11581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11582       ExitAnalyzeMode();
11583
11584     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11585         gameMode == AnalyzeFile)
11586         TruncateGame();
11587
11588     ResurrectChessProgram();    /* in case it isn't running */
11589     gameMode = MachinePlaysBlack;
11590     pausing = FALSE;
11591     ModeHighlight();
11592     SetGameInfo();
11593     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11594     DisplayTitle(buf);
11595     if (first.sendName) {
11596       sprintf(buf, "name %s\n", gameInfo.white);
11597       SendToProgram(buf, &first);
11598     }
11599     if (first.sendTime) {
11600       if (first.useColors) {
11601         SendToProgram("white\n", &first); /*gnu kludge*/
11602       }
11603       SendTimeRemaining(&first, FALSE);
11604     }
11605     if (first.useColors) {
11606       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11607     }
11608     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11609     SetMachineThinkingEnables();
11610     first.maybeThinking = TRUE;
11611     StartClocks();
11612
11613     if (appData.autoFlipView && flipView) {
11614       flipView = !flipView;
11615       DrawPosition(FALSE, NULL);
11616       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11617     }
11618     if(bookHit) { // [HGM] book: simulate book reply
11619         static char bookMove[MSG_SIZ]; // a bit generous?
11620
11621         programStats.nodes = programStats.depth = programStats.time = 
11622         programStats.score = programStats.got_only_move = 0;
11623         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11624
11625         strcpy(bookMove, "move ");
11626         strcat(bookMove, bookHit);
11627         HandleMachineMove(bookMove, &first);
11628     }
11629 }
11630
11631
11632 void
11633 DisplayTwoMachinesTitle()
11634 {
11635     char buf[MSG_SIZ];
11636     if (appData.matchGames > 0) {
11637         if (first.twoMachinesColor[0] == 'w') {
11638             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11639                     gameInfo.white, gameInfo.black,
11640                     first.matchWins, second.matchWins,
11641                     matchGame - 1 - (first.matchWins + second.matchWins));
11642         } else {
11643             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11644                     gameInfo.white, gameInfo.black,
11645                     second.matchWins, first.matchWins,
11646                     matchGame - 1 - (first.matchWins + second.matchWins));
11647         }
11648     } else {
11649         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11650     }
11651     DisplayTitle(buf);
11652 }
11653
11654 void
11655 TwoMachinesEvent P((void))
11656 {
11657     int i;
11658     char buf[MSG_SIZ];
11659     ChessProgramState *onmove;
11660     char *bookHit = NULL;
11661     
11662     if (appData.noChessProgram) return;
11663
11664     switch (gameMode) {
11665       case TwoMachinesPlay:
11666         return;
11667       case MachinePlaysWhite:
11668       case MachinePlaysBlack:
11669         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11670             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11671             return;
11672         }
11673         /* fall through */
11674       case BeginningOfGame:
11675       case PlayFromGameFile:
11676       case EndOfGame:
11677         EditGameEvent();
11678         if (gameMode != EditGame) return;
11679         break;
11680       case EditPosition:
11681         EditPositionDone(TRUE);
11682         break;
11683       case AnalyzeMode:
11684       case AnalyzeFile:
11685         ExitAnalyzeMode();
11686         break;
11687       case EditGame:
11688       default:
11689         break;
11690     }
11691
11692 //    forwardMostMove = currentMove;
11693     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11694     ResurrectChessProgram();    /* in case first program isn't running */
11695
11696     if (second.pr == NULL) {
11697         StartChessProgram(&second);
11698         if (second.protocolVersion == 1) {
11699           TwoMachinesEventIfReady();
11700         } else {
11701           /* kludge: allow timeout for initial "feature" command */
11702           FreezeUI();
11703           DisplayMessage("", _("Starting second chess program"));
11704           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11705         }
11706         return;
11707     }
11708     DisplayMessage("", "");
11709     InitChessProgram(&second, FALSE);
11710     SendToProgram("force\n", &second);
11711     if (startedFromSetupPosition) {
11712         SendBoard(&second, backwardMostMove);
11713     if (appData.debugMode) {
11714         fprintf(debugFP, "Two Machines\n");
11715     }
11716     }
11717     for (i = backwardMostMove; i < forwardMostMove; i++) {
11718         SendMoveToProgram(i, &second);
11719     }
11720
11721     gameMode = TwoMachinesPlay;
11722     pausing = FALSE;
11723     ModeHighlight();
11724     SetGameInfo();
11725     DisplayTwoMachinesTitle();
11726     firstMove = TRUE;
11727     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11728         onmove = &first;
11729     } else {
11730         onmove = &second;
11731     }
11732
11733     SendToProgram(first.computerString, &first);
11734     if (first.sendName) {
11735       sprintf(buf, "name %s\n", second.tidy);
11736       SendToProgram(buf, &first);
11737     }
11738     SendToProgram(second.computerString, &second);
11739     if (second.sendName) {
11740       sprintf(buf, "name %s\n", first.tidy);
11741       SendToProgram(buf, &second);
11742     }
11743
11744     ResetClocks();
11745     if (!first.sendTime || !second.sendTime) {
11746         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11747         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11748     }
11749     if (onmove->sendTime) {
11750       if (onmove->useColors) {
11751         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11752       }
11753       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11754     }
11755     if (onmove->useColors) {
11756       SendToProgram(onmove->twoMachinesColor, onmove);
11757     }
11758     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11759 //    SendToProgram("go\n", onmove);
11760     onmove->maybeThinking = TRUE;
11761     SetMachineThinkingEnables();
11762
11763     StartClocks();
11764
11765     if(bookHit) { // [HGM] book: simulate book reply
11766         static char bookMove[MSG_SIZ]; // a bit generous?
11767
11768         programStats.nodes = programStats.depth = programStats.time = 
11769         programStats.score = programStats.got_only_move = 0;
11770         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11771
11772         strcpy(bookMove, "move ");
11773         strcat(bookMove, bookHit);
11774         savedMessage = bookMove; // args for deferred call
11775         savedState = onmove;
11776         ScheduleDelayedEvent(DeferredBookMove, 1);
11777     }
11778 }
11779
11780 void
11781 TrainingEvent()
11782 {
11783     if (gameMode == Training) {
11784       SetTrainingModeOff();
11785       gameMode = PlayFromGameFile;
11786       DisplayMessage("", _("Training mode off"));
11787     } else {
11788       gameMode = Training;
11789       animateTraining = appData.animate;
11790
11791       /* make sure we are not already at the end of the game */
11792       if (currentMove < forwardMostMove) {
11793         SetTrainingModeOn();
11794         DisplayMessage("", _("Training mode on"));
11795       } else {
11796         gameMode = PlayFromGameFile;
11797         DisplayError(_("Already at end of game"), 0);
11798       }
11799     }
11800     ModeHighlight();
11801 }
11802
11803 void
11804 IcsClientEvent()
11805 {
11806     if (!appData.icsActive) return;
11807     switch (gameMode) {
11808       case IcsPlayingWhite:
11809       case IcsPlayingBlack:
11810       case IcsObserving:
11811       case IcsIdle:
11812       case BeginningOfGame:
11813       case IcsExamining:
11814         return;
11815
11816       case EditGame:
11817         break;
11818
11819       case EditPosition:
11820         EditPositionDone(TRUE);
11821         break;
11822
11823       case AnalyzeMode:
11824       case AnalyzeFile:
11825         ExitAnalyzeMode();
11826         break;
11827         
11828       default:
11829         EditGameEvent();
11830         break;
11831     }
11832
11833     gameMode = IcsIdle;
11834     ModeHighlight();
11835     return;
11836 }
11837
11838
11839 void
11840 EditGameEvent()
11841 {
11842     int i;
11843
11844     switch (gameMode) {
11845       case Training:
11846         SetTrainingModeOff();
11847         break;
11848       case MachinePlaysWhite:
11849       case MachinePlaysBlack:
11850       case BeginningOfGame:
11851         SendToProgram("force\n", &first);
11852         SetUserThinkingEnables();
11853         break;
11854       case PlayFromGameFile:
11855         (void) StopLoadGameTimer();
11856         if (gameFileFP != NULL) {
11857             gameFileFP = NULL;
11858         }
11859         break;
11860       case EditPosition:
11861         EditPositionDone(TRUE);
11862         break;
11863       case AnalyzeMode:
11864       case AnalyzeFile:
11865         ExitAnalyzeMode();
11866         SendToProgram("force\n", &first);
11867         break;
11868       case TwoMachinesPlay:
11869         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11870         ResurrectChessProgram();
11871         SetUserThinkingEnables();
11872         break;
11873       case EndOfGame:
11874         ResurrectChessProgram();
11875         break;
11876       case IcsPlayingBlack:
11877       case IcsPlayingWhite:
11878         DisplayError(_("Warning: You are still playing a game"), 0);
11879         break;
11880       case IcsObserving:
11881         DisplayError(_("Warning: You are still observing a game"), 0);
11882         break;
11883       case IcsExamining:
11884         DisplayError(_("Warning: You are still examining a game"), 0);
11885         break;
11886       case IcsIdle:
11887         break;
11888       case EditGame:
11889       default:
11890         return;
11891     }
11892     
11893     pausing = FALSE;
11894     StopClocks();
11895     first.offeredDraw = second.offeredDraw = 0;
11896
11897     if (gameMode == PlayFromGameFile) {
11898         whiteTimeRemaining = timeRemaining[0][currentMove];
11899         blackTimeRemaining = timeRemaining[1][currentMove];
11900         DisplayTitle("");
11901     }
11902
11903     if (gameMode == MachinePlaysWhite ||
11904         gameMode == MachinePlaysBlack ||
11905         gameMode == TwoMachinesPlay ||
11906         gameMode == EndOfGame) {
11907         i = forwardMostMove;
11908         while (i > currentMove) {
11909             SendToProgram("undo\n", &first);
11910             i--;
11911         }
11912         whiteTimeRemaining = timeRemaining[0][currentMove];
11913         blackTimeRemaining = timeRemaining[1][currentMove];
11914         DisplayBothClocks();
11915         if (whiteFlag || blackFlag) {
11916             whiteFlag = blackFlag = 0;
11917         }
11918         DisplayTitle("");
11919     }           
11920     
11921     gameMode = EditGame;
11922     ModeHighlight();
11923     SetGameInfo();
11924 }
11925
11926
11927 void
11928 EditPositionEvent()
11929 {
11930     if (gameMode == EditPosition) {
11931         EditGameEvent();
11932         return;
11933     }
11934     
11935     EditGameEvent();
11936     if (gameMode != EditGame) return;
11937     
11938     gameMode = EditPosition;
11939     ModeHighlight();
11940     SetGameInfo();
11941     if (currentMove > 0)
11942       CopyBoard(boards[0], boards[currentMove]);
11943     
11944     blackPlaysFirst = !WhiteOnMove(currentMove);
11945     ResetClocks();
11946     currentMove = forwardMostMove = backwardMostMove = 0;
11947     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11948     DisplayMove(-1);
11949 }
11950
11951 void
11952 ExitAnalyzeMode()
11953 {
11954     /* [DM] icsEngineAnalyze - possible call from other functions */
11955     if (appData.icsEngineAnalyze) {
11956         appData.icsEngineAnalyze = FALSE;
11957
11958         DisplayMessage("",_("Close ICS engine analyze..."));
11959     }
11960     if (first.analysisSupport && first.analyzing) {
11961       SendToProgram("exit\n", &first);
11962       first.analyzing = FALSE;
11963     }
11964     thinkOutput[0] = NULLCHAR;
11965 }
11966
11967 void
11968 EditPositionDone(Boolean fakeRights)
11969 {
11970     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11971
11972     startedFromSetupPosition = TRUE;
11973     InitChessProgram(&first, FALSE);
11974     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11975       boards[0][EP_STATUS] = EP_NONE;
11976       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11977     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11978         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11979         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11980       } else boards[0][CASTLING][2] = NoRights;
11981     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11982         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11983         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11984       } else boards[0][CASTLING][5] = NoRights;
11985     }
11986     SendToProgram("force\n", &first);
11987     if (blackPlaysFirst) {
11988         strcpy(moveList[0], "");
11989         strcpy(parseList[0], "");
11990         currentMove = forwardMostMove = backwardMostMove = 1;
11991         CopyBoard(boards[1], boards[0]);
11992     } else {
11993         currentMove = forwardMostMove = backwardMostMove = 0;
11994     }
11995     SendBoard(&first, forwardMostMove);
11996     if (appData.debugMode) {
11997         fprintf(debugFP, "EditPosDone\n");
11998     }
11999     DisplayTitle("");
12000     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12001     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12002     gameMode = EditGame;
12003     ModeHighlight();
12004     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12005     ClearHighlights(); /* [AS] */
12006 }
12007
12008 /* Pause for `ms' milliseconds */
12009 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12010 void
12011 TimeDelay(ms)
12012      long ms;
12013 {
12014     TimeMark m1, m2;
12015
12016     GetTimeMark(&m1);
12017     do {
12018         GetTimeMark(&m2);
12019     } while (SubtractTimeMarks(&m2, &m1) < ms);
12020 }
12021
12022 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12023 void
12024 SendMultiLineToICS(buf)
12025      char *buf;
12026 {
12027     char temp[MSG_SIZ+1], *p;
12028     int len;
12029
12030     len = strlen(buf);
12031     if (len > MSG_SIZ)
12032       len = MSG_SIZ;
12033   
12034     strncpy(temp, buf, len);
12035     temp[len] = 0;
12036
12037     p = temp;
12038     while (*p) {
12039         if (*p == '\n' || *p == '\r')
12040           *p = ' ';
12041         ++p;
12042     }
12043
12044     strcat(temp, "\n");
12045     SendToICS(temp);
12046     SendToPlayer(temp, strlen(temp));
12047 }
12048
12049 void
12050 SetWhiteToPlayEvent()
12051 {
12052     if (gameMode == EditPosition) {
12053         blackPlaysFirst = FALSE;
12054         DisplayBothClocks();    /* works because currentMove is 0 */
12055     } else if (gameMode == IcsExamining) {
12056         SendToICS(ics_prefix);
12057         SendToICS("tomove white\n");
12058     }
12059 }
12060
12061 void
12062 SetBlackToPlayEvent()
12063 {
12064     if (gameMode == EditPosition) {
12065         blackPlaysFirst = TRUE;
12066         currentMove = 1;        /* kludge */
12067         DisplayBothClocks();
12068         currentMove = 0;
12069     } else if (gameMode == IcsExamining) {
12070         SendToICS(ics_prefix);
12071         SendToICS("tomove black\n");
12072     }
12073 }
12074
12075 void
12076 EditPositionMenuEvent(selection, x, y)
12077      ChessSquare selection;
12078      int x, y;
12079 {
12080     char buf[MSG_SIZ];
12081     ChessSquare piece = boards[0][y][x];
12082
12083     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12084
12085     switch (selection) {
12086       case ClearBoard:
12087         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12088             SendToICS(ics_prefix);
12089             SendToICS("bsetup clear\n");
12090         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12091             SendToICS(ics_prefix);
12092             SendToICS("clearboard\n");
12093         } else {
12094             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12095                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12096                 for (y = 0; y < BOARD_HEIGHT; y++) {
12097                     if (gameMode == IcsExamining) {
12098                         if (boards[currentMove][y][x] != EmptySquare) {
12099                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12100                                     AAA + x, ONE + y);
12101                             SendToICS(buf);
12102                         }
12103                     } else {
12104                         boards[0][y][x] = p;
12105                     }
12106                 }
12107             }
12108         }
12109         if (gameMode == EditPosition) {
12110             DrawPosition(FALSE, boards[0]);
12111         }
12112         break;
12113
12114       case WhitePlay:
12115         SetWhiteToPlayEvent();
12116         break;
12117
12118       case BlackPlay:
12119         SetBlackToPlayEvent();
12120         break;
12121
12122       case EmptySquare:
12123         if (gameMode == IcsExamining) {
12124             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12125             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12126             SendToICS(buf);
12127         } else {
12128             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12129                 if(x == BOARD_LEFT-2) {
12130                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12131                     boards[0][y][1] = 0;
12132                 } else
12133                 if(x == BOARD_RGHT+1) {
12134                     if(y >= gameInfo.holdingsSize) break;
12135                     boards[0][y][BOARD_WIDTH-2] = 0;
12136                 } else break;
12137             }
12138             boards[0][y][x] = EmptySquare;
12139             DrawPosition(FALSE, boards[0]);
12140         }
12141         break;
12142
12143       case PromotePiece:
12144         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12145            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12146             selection = (ChessSquare) (PROMOTED piece);
12147         } else if(piece == EmptySquare) selection = WhiteSilver;
12148         else selection = (ChessSquare)((int)piece - 1);
12149         goto defaultlabel;
12150
12151       case DemotePiece:
12152         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12153            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12154             selection = (ChessSquare) (DEMOTED piece);
12155         } else if(piece == EmptySquare) selection = BlackSilver;
12156         else selection = (ChessSquare)((int)piece + 1);       
12157         goto defaultlabel;
12158
12159       case WhiteQueen:
12160       case BlackQueen:
12161         if(gameInfo.variant == VariantShatranj ||
12162            gameInfo.variant == VariantXiangqi  ||
12163            gameInfo.variant == VariantCourier  ||
12164            gameInfo.variant == VariantMakruk     )
12165             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12166         goto defaultlabel;
12167
12168       case WhiteKing:
12169       case BlackKing:
12170         if(gameInfo.variant == VariantXiangqi)
12171             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12172         if(gameInfo.variant == VariantKnightmate)
12173             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12174       default:
12175         defaultlabel:
12176         if (gameMode == IcsExamining) {
12177             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12178             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12179                     PieceToChar(selection), AAA + x, ONE + y);
12180             SendToICS(buf);
12181         } else {
12182             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12183                 int n;
12184                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12185                     n = PieceToNumber(selection - BlackPawn);
12186                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12187                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12188                     boards[0][BOARD_HEIGHT-1-n][1]++;
12189                 } else
12190                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12191                     n = PieceToNumber(selection);
12192                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12193                     boards[0][n][BOARD_WIDTH-1] = selection;
12194                     boards[0][n][BOARD_WIDTH-2]++;
12195                 }
12196             } else
12197             boards[0][y][x] = selection;
12198             DrawPosition(TRUE, boards[0]);
12199         }
12200         break;
12201     }
12202 }
12203
12204
12205 void
12206 DropMenuEvent(selection, x, y)
12207      ChessSquare selection;
12208      int x, y;
12209 {
12210     ChessMove moveType;
12211
12212     switch (gameMode) {
12213       case IcsPlayingWhite:
12214       case MachinePlaysBlack:
12215         if (!WhiteOnMove(currentMove)) {
12216             DisplayMoveError(_("It is Black's turn"));
12217             return;
12218         }
12219         moveType = WhiteDrop;
12220         break;
12221       case IcsPlayingBlack:
12222       case MachinePlaysWhite:
12223         if (WhiteOnMove(currentMove)) {
12224             DisplayMoveError(_("It is White's turn"));
12225             return;
12226         }
12227         moveType = BlackDrop;
12228         break;
12229       case EditGame:
12230         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12231         break;
12232       default:
12233         return;
12234     }
12235
12236     if (moveType == BlackDrop && selection < BlackPawn) {
12237       selection = (ChessSquare) ((int) selection
12238                                  + (int) BlackPawn - (int) WhitePawn);
12239     }
12240     if (boards[currentMove][y][x] != EmptySquare) {
12241         DisplayMoveError(_("That square is occupied"));
12242         return;
12243     }
12244
12245     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12246 }
12247
12248 void
12249 AcceptEvent()
12250 {
12251     /* Accept a pending offer of any kind from opponent */
12252     
12253     if (appData.icsActive) {
12254         SendToICS(ics_prefix);
12255         SendToICS("accept\n");
12256     } else if (cmailMsgLoaded) {
12257         if (currentMove == cmailOldMove &&
12258             commentList[cmailOldMove] != NULL &&
12259             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12260                    "Black offers a draw" : "White offers a draw")) {
12261             TruncateGame();
12262             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12263             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12264         } else {
12265             DisplayError(_("There is no pending offer on this move"), 0);
12266             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12267         }
12268     } else {
12269         /* Not used for offers from chess program */
12270     }
12271 }
12272
12273 void
12274 DeclineEvent()
12275 {
12276     /* Decline a pending offer of any kind from opponent */
12277     
12278     if (appData.icsActive) {
12279         SendToICS(ics_prefix);
12280         SendToICS("decline\n");
12281     } else if (cmailMsgLoaded) {
12282         if (currentMove == cmailOldMove &&
12283             commentList[cmailOldMove] != NULL &&
12284             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12285                    "Black offers a draw" : "White offers a draw")) {
12286 #ifdef NOTDEF
12287             AppendComment(cmailOldMove, "Draw declined", TRUE);
12288             DisplayComment(cmailOldMove - 1, "Draw declined");
12289 #endif /*NOTDEF*/
12290         } else {
12291             DisplayError(_("There is no pending offer on this move"), 0);
12292         }
12293     } else {
12294         /* Not used for offers from chess program */
12295     }
12296 }
12297
12298 void
12299 RematchEvent()
12300 {
12301     /* Issue ICS rematch command */
12302     if (appData.icsActive) {
12303         SendToICS(ics_prefix);
12304         SendToICS("rematch\n");
12305     }
12306 }
12307
12308 void
12309 CallFlagEvent()
12310 {
12311     /* Call your opponent's flag (claim a win on time) */
12312     if (appData.icsActive) {
12313         SendToICS(ics_prefix);
12314         SendToICS("flag\n");
12315     } else {
12316         switch (gameMode) {
12317           default:
12318             return;
12319           case MachinePlaysWhite:
12320             if (whiteFlag) {
12321                 if (blackFlag)
12322                   GameEnds(GameIsDrawn, "Both players ran out of time",
12323                            GE_PLAYER);
12324                 else
12325                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12326             } else {
12327                 DisplayError(_("Your opponent is not out of time"), 0);
12328             }
12329             break;
12330           case MachinePlaysBlack:
12331             if (blackFlag) {
12332                 if (whiteFlag)
12333                   GameEnds(GameIsDrawn, "Both players ran out of time",
12334                            GE_PLAYER);
12335                 else
12336                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12337             } else {
12338                 DisplayError(_("Your opponent is not out of time"), 0);
12339             }
12340             break;
12341         }
12342     }
12343 }
12344
12345 void
12346 DrawEvent()
12347 {
12348     /* Offer draw or accept pending draw offer from opponent */
12349     
12350     if (appData.icsActive) {
12351         /* Note: tournament rules require draw offers to be
12352            made after you make your move but before you punch
12353            your clock.  Currently ICS doesn't let you do that;
12354            instead, you immediately punch your clock after making
12355            a move, but you can offer a draw at any time. */
12356         
12357         SendToICS(ics_prefix);
12358         SendToICS("draw\n");
12359         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12360     } else if (cmailMsgLoaded) {
12361         if (currentMove == cmailOldMove &&
12362             commentList[cmailOldMove] != NULL &&
12363             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12364                    "Black offers a draw" : "White offers a draw")) {
12365             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12366             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12367         } else if (currentMove == cmailOldMove + 1) {
12368             char *offer = WhiteOnMove(cmailOldMove) ?
12369               "White offers a draw" : "Black offers a draw";
12370             AppendComment(currentMove, offer, TRUE);
12371             DisplayComment(currentMove - 1, offer);
12372             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12373         } else {
12374             DisplayError(_("You must make your move before offering a draw"), 0);
12375             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12376         }
12377     } else if (first.offeredDraw) {
12378         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12379     } else {
12380         if (first.sendDrawOffers) {
12381             SendToProgram("draw\n", &first);
12382             userOfferedDraw = TRUE;
12383         }
12384     }
12385 }
12386
12387 void
12388 AdjournEvent()
12389 {
12390     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12391     
12392     if (appData.icsActive) {
12393         SendToICS(ics_prefix);
12394         SendToICS("adjourn\n");
12395     } else {
12396         /* Currently GNU Chess doesn't offer or accept Adjourns */
12397     }
12398 }
12399
12400
12401 void
12402 AbortEvent()
12403 {
12404     /* Offer Abort or accept pending Abort offer from opponent */
12405     
12406     if (appData.icsActive) {
12407         SendToICS(ics_prefix);
12408         SendToICS("abort\n");
12409     } else {
12410         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12411     }
12412 }
12413
12414 void
12415 ResignEvent()
12416 {
12417     /* Resign.  You can do this even if it's not your turn. */
12418     
12419     if (appData.icsActive) {
12420         SendToICS(ics_prefix);
12421         SendToICS("resign\n");
12422     } else {
12423         switch (gameMode) {
12424           case MachinePlaysWhite:
12425             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12426             break;
12427           case MachinePlaysBlack:
12428             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12429             break;
12430           case EditGame:
12431             if (cmailMsgLoaded) {
12432                 TruncateGame();
12433                 if (WhiteOnMove(cmailOldMove)) {
12434                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12435                 } else {
12436                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12437                 }
12438                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12439             }
12440             break;
12441           default:
12442             break;
12443         }
12444     }
12445 }
12446
12447
12448 void
12449 StopObservingEvent()
12450 {
12451     /* Stop observing current games */
12452     SendToICS(ics_prefix);
12453     SendToICS("unobserve\n");
12454 }
12455
12456 void
12457 StopExaminingEvent()
12458 {
12459     /* Stop observing current game */
12460     SendToICS(ics_prefix);
12461     SendToICS("unexamine\n");
12462 }
12463
12464 void
12465 ForwardInner(target)
12466      int target;
12467 {
12468     int limit;
12469
12470     if (appData.debugMode)
12471         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12472                 target, currentMove, forwardMostMove);
12473
12474     if (gameMode == EditPosition)
12475       return;
12476
12477     if (gameMode == PlayFromGameFile && !pausing)
12478       PauseEvent();
12479     
12480     if (gameMode == IcsExamining && pausing)
12481       limit = pauseExamForwardMostMove;
12482     else
12483       limit = forwardMostMove;
12484     
12485     if (target > limit) target = limit;
12486
12487     if (target > 0 && moveList[target - 1][0]) {
12488         int fromX, fromY, toX, toY;
12489         toX = moveList[target - 1][2] - AAA;
12490         toY = moveList[target - 1][3] - ONE;
12491         if (moveList[target - 1][1] == '@') {
12492             if (appData.highlightLastMove) {
12493                 SetHighlights(-1, -1, toX, toY);
12494             }
12495         } else {
12496             fromX = moveList[target - 1][0] - AAA;
12497             fromY = moveList[target - 1][1] - ONE;
12498             if (target == currentMove + 1) {
12499                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12500             }
12501             if (appData.highlightLastMove) {
12502                 SetHighlights(fromX, fromY, toX, toY);
12503             }
12504         }
12505     }
12506     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12507         gameMode == Training || gameMode == PlayFromGameFile || 
12508         gameMode == AnalyzeFile) {
12509         while (currentMove < target) {
12510             SendMoveToProgram(currentMove++, &first);
12511         }
12512     } else {
12513         currentMove = target;
12514     }
12515     
12516     if (gameMode == EditGame || gameMode == EndOfGame) {
12517         whiteTimeRemaining = timeRemaining[0][currentMove];
12518         blackTimeRemaining = timeRemaining[1][currentMove];
12519     }
12520     DisplayBothClocks();
12521     DisplayMove(currentMove - 1);
12522     DrawPosition(FALSE, boards[currentMove]);
12523     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12524     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12525         DisplayComment(currentMove - 1, commentList[currentMove]);
12526     }
12527 }
12528
12529
12530 void
12531 ForwardEvent()
12532 {
12533     if (gameMode == IcsExamining && !pausing) {
12534         SendToICS(ics_prefix);
12535         SendToICS("forward\n");
12536     } else {
12537         ForwardInner(currentMove + 1);
12538     }
12539 }
12540
12541 void
12542 ToEndEvent()
12543 {
12544     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12545         /* to optimze, we temporarily turn off analysis mode while we feed
12546          * the remaining moves to the engine. Otherwise we get analysis output
12547          * after each move.
12548          */ 
12549         if (first.analysisSupport) {
12550           SendToProgram("exit\nforce\n", &first);
12551           first.analyzing = FALSE;
12552         }
12553     }
12554         
12555     if (gameMode == IcsExamining && !pausing) {
12556         SendToICS(ics_prefix);
12557         SendToICS("forward 999999\n");
12558     } else {
12559         ForwardInner(forwardMostMove);
12560     }
12561
12562     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12563         /* we have fed all the moves, so reactivate analysis mode */
12564         SendToProgram("analyze\n", &first);
12565         first.analyzing = TRUE;
12566         /*first.maybeThinking = TRUE;*/
12567         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12568     }
12569 }
12570
12571 void
12572 BackwardInner(target)
12573      int target;
12574 {
12575     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12576
12577     if (appData.debugMode)
12578         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12579                 target, currentMove, forwardMostMove);
12580
12581     if (gameMode == EditPosition) return;
12582     if (currentMove <= backwardMostMove) {
12583         ClearHighlights();
12584         DrawPosition(full_redraw, boards[currentMove]);
12585         return;
12586     }
12587     if (gameMode == PlayFromGameFile && !pausing)
12588       PauseEvent();
12589     
12590     if (moveList[target][0]) {
12591         int fromX, fromY, toX, toY;
12592         toX = moveList[target][2] - AAA;
12593         toY = moveList[target][3] - ONE;
12594         if (moveList[target][1] == '@') {
12595             if (appData.highlightLastMove) {
12596                 SetHighlights(-1, -1, toX, toY);
12597             }
12598         } else {
12599             fromX = moveList[target][0] - AAA;
12600             fromY = moveList[target][1] - ONE;
12601             if (target == currentMove - 1) {
12602                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12603             }
12604             if (appData.highlightLastMove) {
12605                 SetHighlights(fromX, fromY, toX, toY);
12606             }
12607         }
12608     }
12609     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12610         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12611         while (currentMove > target) {
12612             SendToProgram("undo\n", &first);
12613             currentMove--;
12614         }
12615     } else {
12616         currentMove = target;
12617     }
12618     
12619     if (gameMode == EditGame || gameMode == EndOfGame) {
12620         whiteTimeRemaining = timeRemaining[0][currentMove];
12621         blackTimeRemaining = timeRemaining[1][currentMove];
12622     }
12623     DisplayBothClocks();
12624     DisplayMove(currentMove - 1);
12625     DrawPosition(full_redraw, boards[currentMove]);
12626     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12627     // [HGM] PV info: routine tests if comment empty
12628     DisplayComment(currentMove - 1, commentList[currentMove]);
12629 }
12630
12631 void
12632 BackwardEvent()
12633 {
12634     if (gameMode == IcsExamining && !pausing) {
12635         SendToICS(ics_prefix);
12636         SendToICS("backward\n");
12637     } else {
12638         BackwardInner(currentMove - 1);
12639     }
12640 }
12641
12642 void
12643 ToStartEvent()
12644 {
12645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12646         /* to optimize, we temporarily turn off analysis mode while we undo
12647          * all the moves. Otherwise we get analysis output after each undo.
12648          */ 
12649         if (first.analysisSupport) {
12650           SendToProgram("exit\nforce\n", &first);
12651           first.analyzing = FALSE;
12652         }
12653     }
12654
12655     if (gameMode == IcsExamining && !pausing) {
12656         SendToICS(ics_prefix);
12657         SendToICS("backward 999999\n");
12658     } else {
12659         BackwardInner(backwardMostMove);
12660     }
12661
12662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12663         /* we have fed all the moves, so reactivate analysis mode */
12664         SendToProgram("analyze\n", &first);
12665         first.analyzing = TRUE;
12666         /*first.maybeThinking = TRUE;*/
12667         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12668     }
12669 }
12670
12671 void
12672 ToNrEvent(int to)
12673 {
12674   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12675   if (to >= forwardMostMove) to = forwardMostMove;
12676   if (to <= backwardMostMove) to = backwardMostMove;
12677   if (to < currentMove) {
12678     BackwardInner(to);
12679   } else {
12680     ForwardInner(to);
12681   }
12682 }
12683
12684 void
12685 RevertEvent(Boolean annotate)
12686 {
12687     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12688         return;
12689     }
12690     if (gameMode != IcsExamining) {
12691         DisplayError(_("You are not examining a game"), 0);
12692         return;
12693     }
12694     if (pausing) {
12695         DisplayError(_("You can't revert while pausing"), 0);
12696         return;
12697     }
12698     SendToICS(ics_prefix);
12699     SendToICS("revert\n");
12700 }
12701
12702 void
12703 RetractMoveEvent()
12704 {
12705     switch (gameMode) {
12706       case MachinePlaysWhite:
12707       case MachinePlaysBlack:
12708         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12709             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12710             return;
12711         }
12712         if (forwardMostMove < 2) return;
12713         currentMove = forwardMostMove = forwardMostMove - 2;
12714         whiteTimeRemaining = timeRemaining[0][currentMove];
12715         blackTimeRemaining = timeRemaining[1][currentMove];
12716         DisplayBothClocks();
12717         DisplayMove(currentMove - 1);
12718         ClearHighlights();/*!! could figure this out*/
12719         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12720         SendToProgram("remove\n", &first);
12721         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12722         break;
12723
12724       case BeginningOfGame:
12725       default:
12726         break;
12727
12728       case IcsPlayingWhite:
12729       case IcsPlayingBlack:
12730         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12731             SendToICS(ics_prefix);
12732             SendToICS("takeback 2\n");
12733         } else {
12734             SendToICS(ics_prefix);
12735             SendToICS("takeback 1\n");
12736         }
12737         break;
12738     }
12739 }
12740
12741 void
12742 MoveNowEvent()
12743 {
12744     ChessProgramState *cps;
12745
12746     switch (gameMode) {
12747       case MachinePlaysWhite:
12748         if (!WhiteOnMove(forwardMostMove)) {
12749             DisplayError(_("It is your turn"), 0);
12750             return;
12751         }
12752         cps = &first;
12753         break;
12754       case MachinePlaysBlack:
12755         if (WhiteOnMove(forwardMostMove)) {
12756             DisplayError(_("It is your turn"), 0);
12757             return;
12758         }
12759         cps = &first;
12760         break;
12761       case TwoMachinesPlay:
12762         if (WhiteOnMove(forwardMostMove) ==
12763             (first.twoMachinesColor[0] == 'w')) {
12764             cps = &first;
12765         } else {
12766             cps = &second;
12767         }
12768         break;
12769       case BeginningOfGame:
12770       default:
12771         return;
12772     }
12773     SendToProgram("?\n", cps);
12774 }
12775
12776 void
12777 TruncateGameEvent()
12778 {
12779     EditGameEvent();
12780     if (gameMode != EditGame) return;
12781     TruncateGame();
12782 }
12783
12784 void
12785 TruncateGame()
12786 {
12787     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12788     if (forwardMostMove > currentMove) {
12789         if (gameInfo.resultDetails != NULL) {
12790             free(gameInfo.resultDetails);
12791             gameInfo.resultDetails = NULL;
12792             gameInfo.result = GameUnfinished;
12793         }
12794         forwardMostMove = currentMove;
12795         HistorySet(parseList, backwardMostMove, forwardMostMove,
12796                    currentMove-1);
12797     }
12798 }
12799
12800 void
12801 HintEvent()
12802 {
12803     if (appData.noChessProgram) return;
12804     switch (gameMode) {
12805       case MachinePlaysWhite:
12806         if (WhiteOnMove(forwardMostMove)) {
12807             DisplayError(_("Wait until your turn"), 0);
12808             return;
12809         }
12810         break;
12811       case BeginningOfGame:
12812       case MachinePlaysBlack:
12813         if (!WhiteOnMove(forwardMostMove)) {
12814             DisplayError(_("Wait until your turn"), 0);
12815             return;
12816         }
12817         break;
12818       default:
12819         DisplayError(_("No hint available"), 0);
12820         return;
12821     }
12822     SendToProgram("hint\n", &first);
12823     hintRequested = TRUE;
12824 }
12825
12826 void
12827 BookEvent()
12828 {
12829     if (appData.noChessProgram) return;
12830     switch (gameMode) {
12831       case MachinePlaysWhite:
12832         if (WhiteOnMove(forwardMostMove)) {
12833             DisplayError(_("Wait until your turn"), 0);
12834             return;
12835         }
12836         break;
12837       case BeginningOfGame:
12838       case MachinePlaysBlack:
12839         if (!WhiteOnMove(forwardMostMove)) {
12840             DisplayError(_("Wait until your turn"), 0);
12841             return;
12842         }
12843         break;
12844       case EditPosition:
12845         EditPositionDone(TRUE);
12846         break;
12847       case TwoMachinesPlay:
12848         return;
12849       default:
12850         break;
12851     }
12852     SendToProgram("bk\n", &first);
12853     bookOutput[0] = NULLCHAR;
12854     bookRequested = TRUE;
12855 }
12856
12857 void
12858 AboutGameEvent()
12859 {
12860     char *tags = PGNTags(&gameInfo);
12861     TagsPopUp(tags, CmailMsg());
12862     free(tags);
12863 }
12864
12865 /* end button procedures */
12866
12867 void
12868 PrintPosition(fp, move)
12869      FILE *fp;
12870      int move;
12871 {
12872     int i, j;
12873     
12874     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12875         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12876             char c = PieceToChar(boards[move][i][j]);
12877             fputc(c == 'x' ? '.' : c, fp);
12878             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12879         }
12880     }
12881     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12882       fprintf(fp, "white to play\n");
12883     else
12884       fprintf(fp, "black to play\n");
12885 }
12886
12887 void
12888 PrintOpponents(fp)
12889      FILE *fp;
12890 {
12891     if (gameInfo.white != NULL) {
12892         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12893     } else {
12894         fprintf(fp, "\n");
12895     }
12896 }
12897
12898 /* Find last component of program's own name, using some heuristics */
12899 void
12900 TidyProgramName(prog, host, buf)
12901      char *prog, *host, buf[MSG_SIZ];
12902 {
12903     char *p, *q;
12904     int local = (strcmp(host, "localhost") == 0);
12905     while (!local && (p = strchr(prog, ';')) != NULL) {
12906         p++;
12907         while (*p == ' ') p++;
12908         prog = p;
12909     }
12910     if (*prog == '"' || *prog == '\'') {
12911         q = strchr(prog + 1, *prog);
12912     } else {
12913         q = strchr(prog, ' ');
12914     }
12915     if (q == NULL) q = prog + strlen(prog);
12916     p = q;
12917     while (p >= prog && *p != '/' && *p != '\\') p--;
12918     p++;
12919     if(p == prog && *p == '"') p++;
12920     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12921     memcpy(buf, p, q - p);
12922     buf[q - p] = NULLCHAR;
12923     if (!local) {
12924         strcat(buf, "@");
12925         strcat(buf, host);
12926     }
12927 }
12928
12929 char *
12930 TimeControlTagValue()
12931 {
12932     char buf[MSG_SIZ];
12933     if (!appData.clockMode) {
12934         strcpy(buf, "-");
12935     } else if (movesPerSession > 0) {
12936         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12937     } else if (timeIncrement == 0) {
12938         sprintf(buf, "%ld", timeControl/1000);
12939     } else {
12940         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12941     }
12942     return StrSave(buf);
12943 }
12944
12945 void
12946 SetGameInfo()
12947 {
12948     /* This routine is used only for certain modes */
12949     VariantClass v = gameInfo.variant;
12950     ChessMove r = GameUnfinished;
12951     char *p = NULL;
12952
12953     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12954         r = gameInfo.result; 
12955         p = gameInfo.resultDetails; 
12956         gameInfo.resultDetails = NULL;
12957     }
12958     ClearGameInfo(&gameInfo);
12959     gameInfo.variant = v;
12960
12961     switch (gameMode) {
12962       case MachinePlaysWhite:
12963         gameInfo.event = StrSave( appData.pgnEventHeader );
12964         gameInfo.site = StrSave(HostName());
12965         gameInfo.date = PGNDate();
12966         gameInfo.round = StrSave("-");
12967         gameInfo.white = StrSave(first.tidy);
12968         gameInfo.black = StrSave(UserName());
12969         gameInfo.timeControl = TimeControlTagValue();
12970         break;
12971
12972       case MachinePlaysBlack:
12973         gameInfo.event = StrSave( appData.pgnEventHeader );
12974         gameInfo.site = StrSave(HostName());
12975         gameInfo.date = PGNDate();
12976         gameInfo.round = StrSave("-");
12977         gameInfo.white = StrSave(UserName());
12978         gameInfo.black = StrSave(first.tidy);
12979         gameInfo.timeControl = TimeControlTagValue();
12980         break;
12981
12982       case TwoMachinesPlay:
12983         gameInfo.event = StrSave( appData.pgnEventHeader );
12984         gameInfo.site = StrSave(HostName());
12985         gameInfo.date = PGNDate();
12986         if (matchGame > 0) {
12987             char buf[MSG_SIZ];
12988             sprintf(buf, "%d", matchGame);
12989             gameInfo.round = StrSave(buf);
12990         } else {
12991             gameInfo.round = StrSave("-");
12992         }
12993         if (first.twoMachinesColor[0] == 'w') {
12994             gameInfo.white = StrSave(first.tidy);
12995             gameInfo.black = StrSave(second.tidy);
12996         } else {
12997             gameInfo.white = StrSave(second.tidy);
12998             gameInfo.black = StrSave(first.tidy);
12999         }
13000         gameInfo.timeControl = TimeControlTagValue();
13001         break;
13002
13003       case EditGame:
13004         gameInfo.event = StrSave("Edited game");
13005         gameInfo.site = StrSave(HostName());
13006         gameInfo.date = PGNDate();
13007         gameInfo.round = StrSave("-");
13008         gameInfo.white = StrSave("-");
13009         gameInfo.black = StrSave("-");
13010         gameInfo.result = r;
13011         gameInfo.resultDetails = p;
13012         break;
13013
13014       case EditPosition:
13015         gameInfo.event = StrSave("Edited position");
13016         gameInfo.site = StrSave(HostName());
13017         gameInfo.date = PGNDate();
13018         gameInfo.round = StrSave("-");
13019         gameInfo.white = StrSave("-");
13020         gameInfo.black = StrSave("-");
13021         break;
13022
13023       case IcsPlayingWhite:
13024       case IcsPlayingBlack:
13025       case IcsObserving:
13026       case IcsExamining:
13027         break;
13028
13029       case PlayFromGameFile:
13030         gameInfo.event = StrSave("Game from non-PGN file");
13031         gameInfo.site = StrSave(HostName());
13032         gameInfo.date = PGNDate();
13033         gameInfo.round = StrSave("-");
13034         gameInfo.white = StrSave("?");
13035         gameInfo.black = StrSave("?");
13036         break;
13037
13038       default:
13039         break;
13040     }
13041 }
13042
13043 void
13044 ReplaceComment(index, text)
13045      int index;
13046      char *text;
13047 {
13048     int len;
13049
13050     while (*text == '\n') text++;
13051     len = strlen(text);
13052     while (len > 0 && text[len - 1] == '\n') len--;
13053
13054     if (commentList[index] != NULL)
13055       free(commentList[index]);
13056
13057     if (len == 0) {
13058         commentList[index] = NULL;
13059         return;
13060     }
13061   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13062       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13063       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13064     commentList[index] = (char *) malloc(len + 2);
13065     strncpy(commentList[index], text, len);
13066     commentList[index][len] = '\n';
13067     commentList[index][len + 1] = NULLCHAR;
13068   } else { 
13069     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13070     char *p;
13071     commentList[index] = (char *) malloc(len + 6);
13072     strcpy(commentList[index], "{\n");
13073     strncpy(commentList[index]+2, text, len);
13074     commentList[index][len+2] = NULLCHAR;
13075     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13076     strcat(commentList[index], "\n}\n");
13077   }
13078 }
13079
13080 void
13081 CrushCRs(text)
13082      char *text;
13083 {
13084   char *p = text;
13085   char *q = text;
13086   char ch;
13087
13088   do {
13089     ch = *p++;
13090     if (ch == '\r') continue;
13091     *q++ = ch;
13092   } while (ch != '\0');
13093 }
13094
13095 void
13096 AppendComment(index, text, addBraces)
13097      int index;
13098      char *text;
13099      Boolean addBraces; // [HGM] braces: tells if we should add {}
13100 {
13101     int oldlen, len;
13102     char *old;
13103
13104 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13105     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13106
13107     CrushCRs(text);
13108     while (*text == '\n') text++;
13109     len = strlen(text);
13110     while (len > 0 && text[len - 1] == '\n') len--;
13111
13112     if (len == 0) return;
13113
13114     if (commentList[index] != NULL) {
13115         old = commentList[index];
13116         oldlen = strlen(old);
13117         while(commentList[index][oldlen-1] ==  '\n')
13118           commentList[index][--oldlen] = NULLCHAR;
13119         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13120         strcpy(commentList[index], old);
13121         free(old);
13122         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13123         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13124           if(addBraces) addBraces = FALSE; else { text++; len--; }
13125           while (*text == '\n') { text++; len--; }
13126           commentList[index][--oldlen] = NULLCHAR;
13127       }
13128         if(addBraces) strcat(commentList[index], "\n{\n");
13129         else          strcat(commentList[index], "\n");
13130         strcat(commentList[index], text);
13131         if(addBraces) strcat(commentList[index], "\n}\n");
13132         else          strcat(commentList[index], "\n");
13133     } else {
13134         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13135         if(addBraces)
13136              strcpy(commentList[index], "{\n");
13137         else commentList[index][0] = NULLCHAR;
13138         strcat(commentList[index], text);
13139         strcat(commentList[index], "\n");
13140         if(addBraces) strcat(commentList[index], "}\n");
13141     }
13142 }
13143
13144 static char * FindStr( char * text, char * sub_text )
13145 {
13146     char * result = strstr( text, sub_text );
13147
13148     if( result != NULL ) {
13149         result += strlen( sub_text );
13150     }
13151
13152     return result;
13153 }
13154
13155 /* [AS] Try to extract PV info from PGN comment */
13156 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13157 char *GetInfoFromComment( int index, char * text )
13158 {
13159     char * sep = text;
13160
13161     if( text != NULL && index > 0 ) {
13162         int score = 0;
13163         int depth = 0;
13164         int time = -1, sec = 0, deci;
13165         char * s_eval = FindStr( text, "[%eval " );
13166         char * s_emt = FindStr( text, "[%emt " );
13167
13168         if( s_eval != NULL || s_emt != NULL ) {
13169             /* New style */
13170             char delim;
13171
13172             if( s_eval != NULL ) {
13173                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13174                     return text;
13175                 }
13176
13177                 if( delim != ']' ) {
13178                     return text;
13179                 }
13180             }
13181
13182             if( s_emt != NULL ) {
13183             }
13184                 return text;
13185         }
13186         else {
13187             /* We expect something like: [+|-]nnn.nn/dd */
13188             int score_lo = 0;
13189
13190             if(*text != '{') return text; // [HGM] braces: must be normal comment
13191
13192             sep = strchr( text, '/' );
13193             if( sep == NULL || sep < (text+4) ) {
13194                 return text;
13195             }
13196
13197             time = -1; sec = -1; deci = -1;
13198             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13199                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13200                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13201                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13202                 return text;
13203             }
13204
13205             if( score_lo < 0 || score_lo >= 100 ) {
13206                 return text;
13207             }
13208
13209             if(sec >= 0) time = 600*time + 10*sec; else
13210             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13211
13212             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13213
13214             /* [HGM] PV time: now locate end of PV info */
13215             while( *++sep >= '0' && *sep <= '9'); // strip depth
13216             if(time >= 0)
13217             while( *++sep >= '0' && *sep <= '9'); // strip time
13218             if(sec >= 0)
13219             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13220             if(deci >= 0)
13221             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13222             while(*sep == ' ') sep++;
13223         }
13224
13225         if( depth <= 0 ) {
13226             return text;
13227         }
13228
13229         if( time < 0 ) {
13230             time = -1;
13231         }
13232
13233         pvInfoList[index-1].depth = depth;
13234         pvInfoList[index-1].score = score;
13235         pvInfoList[index-1].time  = 10*time; // centi-sec
13236         if(*sep == '}') *sep = 0; else *--sep = '{';
13237     }
13238     return sep;
13239 }
13240
13241 void
13242 SendToProgram(message, cps)
13243      char *message;
13244      ChessProgramState *cps;
13245 {
13246     int count, outCount, error;
13247     char buf[MSG_SIZ];
13248
13249     if (cps->pr == NULL) return;
13250     Attention(cps);
13251     
13252     if (appData.debugMode) {
13253         TimeMark now;
13254         GetTimeMark(&now);
13255         fprintf(debugFP, "%ld >%-6s: %s", 
13256                 SubtractTimeMarks(&now, &programStartTime),
13257                 cps->which, message);
13258     }
13259     
13260     count = strlen(message);
13261     outCount = OutputToProcess(cps->pr, message, count, &error);
13262     if (outCount < count && !exiting 
13263                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13264         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13265         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13266             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13267                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13268                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13269             } else {
13270                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13271             }
13272             gameInfo.resultDetails = StrSave(buf);
13273         }
13274         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13275     }
13276 }
13277
13278 void
13279 ReceiveFromProgram(isr, closure, message, count, error)
13280      InputSourceRef isr;
13281      VOIDSTAR closure;
13282      char *message;
13283      int count;
13284      int error;
13285 {
13286     char *end_str;
13287     char buf[MSG_SIZ];
13288     ChessProgramState *cps = (ChessProgramState *)closure;
13289
13290     if (isr != cps->isr) return; /* Killed intentionally */
13291     if (count <= 0) {
13292         if (count == 0) {
13293             sprintf(buf,
13294                     _("Error: %s chess program (%s) exited unexpectedly"),
13295                     cps->which, cps->program);
13296         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13297                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13298                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13299                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13300                 } else {
13301                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13302                 }
13303                 gameInfo.resultDetails = StrSave(buf);
13304             }
13305             RemoveInputSource(cps->isr);
13306             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13307         } else {
13308             sprintf(buf,
13309                     _("Error reading from %s chess program (%s)"),
13310                     cps->which, cps->program);
13311             RemoveInputSource(cps->isr);
13312
13313             /* [AS] Program is misbehaving badly... kill it */
13314             if( count == -2 ) {
13315                 DestroyChildProcess( cps->pr, 9 );
13316                 cps->pr = NoProc;
13317             }
13318
13319             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13320         }
13321         return;
13322     }
13323     
13324     if ((end_str = strchr(message, '\r')) != NULL)
13325       *end_str = NULLCHAR;
13326     if ((end_str = strchr(message, '\n')) != NULL)
13327       *end_str = NULLCHAR;
13328     
13329     if (appData.debugMode) {
13330         TimeMark now; int print = 1;
13331         char *quote = ""; char c; int i;
13332
13333         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13334                 char start = message[0];
13335                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13336                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13337                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13338                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13339                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13340                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13341                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13342                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13343                         { quote = "# "; print = (appData.engineComments == 2); }
13344                 message[0] = start; // restore original message
13345         }
13346         if(print) {
13347                 GetTimeMark(&now);
13348                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13349                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13350                         quote,
13351                         message);
13352         }
13353     }
13354
13355     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13356     if (appData.icsEngineAnalyze) {
13357         if (strstr(message, "whisper") != NULL ||
13358              strstr(message, "kibitz") != NULL || 
13359             strstr(message, "tellics") != NULL) return;
13360     }
13361
13362     HandleMachineMove(message, cps);
13363 }
13364
13365
13366 void
13367 SendTimeControl(cps, mps, tc, inc, sd, st)
13368      ChessProgramState *cps;
13369      int mps, inc, sd, st;
13370      long tc;
13371 {
13372     char buf[MSG_SIZ];
13373     int seconds;
13374
13375     if( timeControl_2 > 0 ) {
13376         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13377             tc = timeControl_2;
13378         }
13379     }
13380     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13381     inc /= cps->timeOdds;
13382     st  /= cps->timeOdds;
13383
13384     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13385
13386     if (st > 0) {
13387       /* Set exact time per move, normally using st command */
13388       if (cps->stKludge) {
13389         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13390         seconds = st % 60;
13391         if (seconds == 0) {
13392           sprintf(buf, "level 1 %d\n", st/60);
13393         } else {
13394           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13395         }
13396       } else {
13397         sprintf(buf, "st %d\n", st);
13398       }
13399     } else {
13400       /* Set conventional or incremental time control, using level command */
13401       if (seconds == 0) {
13402         /* Note old gnuchess bug -- minutes:seconds used to not work.
13403            Fixed in later versions, but still avoid :seconds
13404            when seconds is 0. */
13405         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13406       } else {
13407         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13408                 seconds, inc/1000);
13409       }
13410     }
13411     SendToProgram(buf, cps);
13412
13413     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13414     /* Orthogonally, limit search to given depth */
13415     if (sd > 0) {
13416       if (cps->sdKludge) {
13417         sprintf(buf, "depth\n%d\n", sd);
13418       } else {
13419         sprintf(buf, "sd %d\n", sd);
13420       }
13421       SendToProgram(buf, cps);
13422     }
13423
13424     if(cps->nps > 0) { /* [HGM] nps */
13425         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13426         else {
13427                 sprintf(buf, "nps %d\n", cps->nps);
13428               SendToProgram(buf, cps);
13429         }
13430     }
13431 }
13432
13433 ChessProgramState *WhitePlayer()
13434 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13435 {
13436     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13437        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13438         return &second;
13439     return &first;
13440 }
13441
13442 void
13443 SendTimeRemaining(cps, machineWhite)
13444      ChessProgramState *cps;
13445      int /*boolean*/ machineWhite;
13446 {
13447     char message[MSG_SIZ];
13448     long time, otime;
13449
13450     /* Note: this routine must be called when the clocks are stopped
13451        or when they have *just* been set or switched; otherwise
13452        it will be off by the time since the current tick started.
13453     */
13454     if (machineWhite) {
13455         time = whiteTimeRemaining / 10;
13456         otime = blackTimeRemaining / 10;
13457     } else {
13458         time = blackTimeRemaining / 10;
13459         otime = whiteTimeRemaining / 10;
13460     }
13461     /* [HGM] translate opponent's time by time-odds factor */
13462     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13463     if (appData.debugMode) {
13464         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13465     }
13466
13467     if (time <= 0) time = 1;
13468     if (otime <= 0) otime = 1;
13469     
13470     sprintf(message, "time %ld\n", time);
13471     SendToProgram(message, cps);
13472
13473     sprintf(message, "otim %ld\n", otime);
13474     SendToProgram(message, cps);
13475 }
13476
13477 int
13478 BoolFeature(p, name, loc, cps)
13479      char **p;
13480      char *name;
13481      int *loc;
13482      ChessProgramState *cps;
13483 {
13484   char buf[MSG_SIZ];
13485   int len = strlen(name);
13486   int val;
13487   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13488     (*p) += len + 1;
13489     sscanf(*p, "%d", &val);
13490     *loc = (val != 0);
13491     while (**p && **p != ' ') (*p)++;
13492     sprintf(buf, "accepted %s\n", name);
13493     SendToProgram(buf, cps);
13494     return TRUE;
13495   }
13496   return FALSE;
13497 }
13498
13499 int
13500 IntFeature(p, name, loc, cps)
13501      char **p;
13502      char *name;
13503      int *loc;
13504      ChessProgramState *cps;
13505 {
13506   char buf[MSG_SIZ];
13507   int len = strlen(name);
13508   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13509     (*p) += len + 1;
13510     sscanf(*p, "%d", loc);
13511     while (**p && **p != ' ') (*p)++;
13512     sprintf(buf, "accepted %s\n", name);
13513     SendToProgram(buf, cps);
13514     return TRUE;
13515   }
13516   return FALSE;
13517 }
13518
13519 int
13520 StringFeature(p, name, loc, cps)
13521      char **p;
13522      char *name;
13523      char loc[];
13524      ChessProgramState *cps;
13525 {
13526   char buf[MSG_SIZ];
13527   int len = strlen(name);
13528   if (strncmp((*p), name, len) == 0
13529       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13530     (*p) += len + 2;
13531     sscanf(*p, "%[^\"]", loc);
13532     while (**p && **p != '\"') (*p)++;
13533     if (**p == '\"') (*p)++;
13534     sprintf(buf, "accepted %s\n", name);
13535     SendToProgram(buf, cps);
13536     return TRUE;
13537   }
13538   return FALSE;
13539 }
13540
13541 int 
13542 ParseOption(Option *opt, ChessProgramState *cps)
13543 // [HGM] options: process the string that defines an engine option, and determine
13544 // name, type, default value, and allowed value range
13545 {
13546         char *p, *q, buf[MSG_SIZ];
13547         int n, min = (-1)<<31, max = 1<<31, def;
13548
13549         if(p = strstr(opt->name, " -spin ")) {
13550             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13551             if(max < min) max = min; // enforce consistency
13552             if(def < min) def = min;
13553             if(def > max) def = max;
13554             opt->value = def;
13555             opt->min = min;
13556             opt->max = max;
13557             opt->type = Spin;
13558         } else if((p = strstr(opt->name, " -slider "))) {
13559             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13560             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13561             if(max < min) max = min; // enforce consistency
13562             if(def < min) def = min;
13563             if(def > max) def = max;
13564             opt->value = def;
13565             opt->min = min;
13566             opt->max = max;
13567             opt->type = Spin; // Slider;
13568         } else if((p = strstr(opt->name, " -string "))) {
13569             opt->textValue = p+9;
13570             opt->type = TextBox;
13571         } else if((p = strstr(opt->name, " -file "))) {
13572             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13573             opt->textValue = p+7;
13574             opt->type = TextBox; // FileName;
13575         } else if((p = strstr(opt->name, " -path "))) {
13576             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13577             opt->textValue = p+7;
13578             opt->type = TextBox; // PathName;
13579         } else if(p = strstr(opt->name, " -check ")) {
13580             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13581             opt->value = (def != 0);
13582             opt->type = CheckBox;
13583         } else if(p = strstr(opt->name, " -combo ")) {
13584             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13585             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13586             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13587             opt->value = n = 0;
13588             while(q = StrStr(q, " /// ")) {
13589                 n++; *q = 0;    // count choices, and null-terminate each of them
13590                 q += 5;
13591                 if(*q == '*') { // remember default, which is marked with * prefix
13592                     q++;
13593                     opt->value = n;
13594                 }
13595                 cps->comboList[cps->comboCnt++] = q;
13596             }
13597             cps->comboList[cps->comboCnt++] = NULL;
13598             opt->max = n + 1;
13599             opt->type = ComboBox;
13600         } else if(p = strstr(opt->name, " -button")) {
13601             opt->type = Button;
13602         } else if(p = strstr(opt->name, " -save")) {
13603             opt->type = SaveButton;
13604         } else return FALSE;
13605         *p = 0; // terminate option name
13606         // now look if the command-line options define a setting for this engine option.
13607         if(cps->optionSettings && cps->optionSettings[0])
13608             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13609         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13610                 sprintf(buf, "option %s", p);
13611                 if(p = strstr(buf, ",")) *p = 0;
13612                 strcat(buf, "\n");
13613                 SendToProgram(buf, cps);
13614         }
13615         return TRUE;
13616 }
13617
13618 void
13619 FeatureDone(cps, val)
13620      ChessProgramState* cps;
13621      int val;
13622 {
13623   DelayedEventCallback cb = GetDelayedEvent();
13624   if ((cb == InitBackEnd3 && cps == &first) ||
13625       (cb == TwoMachinesEventIfReady && cps == &second)) {
13626     CancelDelayedEvent();
13627     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13628   }
13629   cps->initDone = val;
13630 }
13631
13632 /* Parse feature command from engine */
13633 void
13634 ParseFeatures(args, cps)
13635      char* args;
13636      ChessProgramState *cps;  
13637 {
13638   char *p = args;
13639   char *q;
13640   int val;
13641   char buf[MSG_SIZ];
13642
13643   for (;;) {
13644     while (*p == ' ') p++;
13645     if (*p == NULLCHAR) return;
13646
13647     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13648     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13649     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13650     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13651     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13652     if (BoolFeature(&p, "reuse", &val, cps)) {
13653       /* Engine can disable reuse, but can't enable it if user said no */
13654       if (!val) cps->reuse = FALSE;
13655       continue;
13656     }
13657     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13658     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13659       if (gameMode == TwoMachinesPlay) {
13660         DisplayTwoMachinesTitle();
13661       } else {
13662         DisplayTitle("");
13663       }
13664       continue;
13665     }
13666     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13667     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13668     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13669     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13670     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13671     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13672     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13673     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13674     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13675     if (IntFeature(&p, "done", &val, cps)) {
13676       FeatureDone(cps, val);
13677       continue;
13678     }
13679     /* Added by Tord: */
13680     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13681     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13682     /* End of additions by Tord */
13683
13684     /* [HGM] added features: */
13685     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13686     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13687     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13688     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13689     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13690     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13691     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13692         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13693             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13694             SendToProgram(buf, cps);
13695             continue;
13696         }
13697         if(cps->nrOptions >= MAX_OPTIONS) {
13698             cps->nrOptions--;
13699             sprintf(buf, "%s engine has too many options\n", cps->which);
13700             DisplayError(buf, 0);
13701         }
13702         continue;
13703     }
13704     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13705     /* End of additions by HGM */
13706
13707     /* unknown feature: complain and skip */
13708     q = p;
13709     while (*q && *q != '=') q++;
13710     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13711     SendToProgram(buf, cps);
13712     p = q;
13713     if (*p == '=') {
13714       p++;
13715       if (*p == '\"') {
13716         p++;
13717         while (*p && *p != '\"') p++;
13718         if (*p == '\"') p++;
13719       } else {
13720         while (*p && *p != ' ') p++;
13721       }
13722     }
13723   }
13724
13725 }
13726
13727 void
13728 PeriodicUpdatesEvent(newState)
13729      int newState;
13730 {
13731     if (newState == appData.periodicUpdates)
13732       return;
13733
13734     appData.periodicUpdates=newState;
13735
13736     /* Display type changes, so update it now */
13737 //    DisplayAnalysis();
13738
13739     /* Get the ball rolling again... */
13740     if (newState) {
13741         AnalysisPeriodicEvent(1);
13742         StartAnalysisClock();
13743     }
13744 }
13745
13746 void
13747 PonderNextMoveEvent(newState)
13748      int newState;
13749 {
13750     if (newState == appData.ponderNextMove) return;
13751     if (gameMode == EditPosition) EditPositionDone(TRUE);
13752     if (newState) {
13753         SendToProgram("hard\n", &first);
13754         if (gameMode == TwoMachinesPlay) {
13755             SendToProgram("hard\n", &second);
13756         }
13757     } else {
13758         SendToProgram("easy\n", &first);
13759         thinkOutput[0] = NULLCHAR;
13760         if (gameMode == TwoMachinesPlay) {
13761             SendToProgram("easy\n", &second);
13762         }
13763     }
13764     appData.ponderNextMove = newState;
13765 }
13766
13767 void
13768 NewSettingEvent(option, command, value)
13769      char *command;
13770      int option, value;
13771 {
13772     char buf[MSG_SIZ];
13773
13774     if (gameMode == EditPosition) EditPositionDone(TRUE);
13775     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13776     SendToProgram(buf, &first);
13777     if (gameMode == TwoMachinesPlay) {
13778         SendToProgram(buf, &second);
13779     }
13780 }
13781
13782 void
13783 ShowThinkingEvent()
13784 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13785 {
13786     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13787     int newState = appData.showThinking
13788         // [HGM] thinking: other features now need thinking output as well
13789         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13790     
13791     if (oldState == newState) return;
13792     oldState = newState;
13793     if (gameMode == EditPosition) EditPositionDone(TRUE);
13794     if (oldState) {
13795         SendToProgram("post\n", &first);
13796         if (gameMode == TwoMachinesPlay) {
13797             SendToProgram("post\n", &second);
13798         }
13799     } else {
13800         SendToProgram("nopost\n", &first);
13801         thinkOutput[0] = NULLCHAR;
13802         if (gameMode == TwoMachinesPlay) {
13803             SendToProgram("nopost\n", &second);
13804         }
13805     }
13806 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13807 }
13808
13809 void
13810 AskQuestionEvent(title, question, replyPrefix, which)
13811      char *title; char *question; char *replyPrefix; char *which;
13812 {
13813   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13814   if (pr == NoProc) return;
13815   AskQuestion(title, question, replyPrefix, pr);
13816 }
13817
13818 void
13819 DisplayMove(moveNumber)
13820      int moveNumber;
13821 {
13822     char message[MSG_SIZ];
13823     char res[MSG_SIZ];
13824     char cpThinkOutput[MSG_SIZ];
13825
13826     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13827     
13828     if (moveNumber == forwardMostMove - 1 || 
13829         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13830
13831         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13832
13833         if (strchr(cpThinkOutput, '\n')) {
13834             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13835         }
13836     } else {
13837         *cpThinkOutput = NULLCHAR;
13838     }
13839
13840     /* [AS] Hide thinking from human user */
13841     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13842         *cpThinkOutput = NULLCHAR;
13843         if( thinkOutput[0] != NULLCHAR ) {
13844             int i;
13845
13846             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13847                 cpThinkOutput[i] = '.';
13848             }
13849             cpThinkOutput[i] = NULLCHAR;
13850             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13851         }
13852     }
13853
13854     if (moveNumber == forwardMostMove - 1 &&
13855         gameInfo.resultDetails != NULL) {
13856         if (gameInfo.resultDetails[0] == NULLCHAR) {
13857             sprintf(res, " %s", PGNResult(gameInfo.result));
13858         } else {
13859             sprintf(res, " {%s} %s",
13860                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13861         }
13862     } else {
13863         res[0] = NULLCHAR;
13864     }
13865
13866     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13867         DisplayMessage(res, cpThinkOutput);
13868     } else {
13869         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13870                 WhiteOnMove(moveNumber) ? " " : ".. ",
13871                 parseList[moveNumber], res);
13872         DisplayMessage(message, cpThinkOutput);
13873     }
13874 }
13875
13876 void
13877 DisplayComment(moveNumber, text)
13878      int moveNumber;
13879      char *text;
13880 {
13881     char title[MSG_SIZ];
13882     char buf[8000]; // comment can be long!
13883     int score, depth;
13884     
13885     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13886       strcpy(title, "Comment");
13887     } else {
13888       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13889               WhiteOnMove(moveNumber) ? " " : ".. ",
13890               parseList[moveNumber]);
13891     }
13892     // [HGM] PV info: display PV info together with (or as) comment
13893     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13894       if(text == NULL) text = "";                                           
13895       score = pvInfoList[moveNumber].score;
13896       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13897               depth, (pvInfoList[moveNumber].time+50)/100, text);
13898       text = buf;
13899     }
13900     if (text != NULL && (appData.autoDisplayComment || commentUp))
13901         CommentPopUp(title, text);
13902 }
13903
13904 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13905  * might be busy thinking or pondering.  It can be omitted if your
13906  * gnuchess is configured to stop thinking immediately on any user
13907  * input.  However, that gnuchess feature depends on the FIONREAD
13908  * ioctl, which does not work properly on some flavors of Unix.
13909  */
13910 void
13911 Attention(cps)
13912      ChessProgramState *cps;
13913 {
13914 #if ATTENTION
13915     if (!cps->useSigint) return;
13916     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13917     switch (gameMode) {
13918       case MachinePlaysWhite:
13919       case MachinePlaysBlack:
13920       case TwoMachinesPlay:
13921       case IcsPlayingWhite:
13922       case IcsPlayingBlack:
13923       case AnalyzeMode:
13924       case AnalyzeFile:
13925         /* Skip if we know it isn't thinking */
13926         if (!cps->maybeThinking) return;
13927         if (appData.debugMode)
13928           fprintf(debugFP, "Interrupting %s\n", cps->which);
13929         InterruptChildProcess(cps->pr);
13930         cps->maybeThinking = FALSE;
13931         break;
13932       default:
13933         break;
13934     }
13935 #endif /*ATTENTION*/
13936 }
13937
13938 int
13939 CheckFlags()
13940 {
13941     if (whiteTimeRemaining <= 0) {
13942         if (!whiteFlag) {
13943             whiteFlag = TRUE;
13944             if (appData.icsActive) {
13945                 if (appData.autoCallFlag &&
13946                     gameMode == IcsPlayingBlack && !blackFlag) {
13947                   SendToICS(ics_prefix);
13948                   SendToICS("flag\n");
13949                 }
13950             } else {
13951                 if (blackFlag) {
13952                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13953                 } else {
13954                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13955                     if (appData.autoCallFlag) {
13956                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13957                         return TRUE;
13958                     }
13959                 }
13960             }
13961         }
13962     }
13963     if (blackTimeRemaining <= 0) {
13964         if (!blackFlag) {
13965             blackFlag = TRUE;
13966             if (appData.icsActive) {
13967                 if (appData.autoCallFlag &&
13968                     gameMode == IcsPlayingWhite && !whiteFlag) {
13969                   SendToICS(ics_prefix);
13970                   SendToICS("flag\n");
13971                 }
13972             } else {
13973                 if (whiteFlag) {
13974                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13975                 } else {
13976                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13977                     if (appData.autoCallFlag) {
13978                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13979                         return TRUE;
13980                     }
13981                 }
13982             }
13983         }
13984     }
13985     return FALSE;
13986 }
13987
13988 void
13989 CheckTimeControl()
13990 {
13991     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13992         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13993
13994     /*
13995      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13996      */
13997     if ( !WhiteOnMove(forwardMostMove) )
13998         /* White made time control */
13999         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14000         /* [HGM] time odds: correct new time quota for time odds! */
14001                                             / WhitePlayer()->timeOdds;
14002       else
14003         /* Black made time control */
14004         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
14005                                             / WhitePlayer()->other->timeOdds;
14006 }
14007
14008 void
14009 DisplayBothClocks()
14010 {
14011     int wom = gameMode == EditPosition ?
14012       !blackPlaysFirst : WhiteOnMove(currentMove);
14013     DisplayWhiteClock(whiteTimeRemaining, wom);
14014     DisplayBlackClock(blackTimeRemaining, !wom);
14015 }
14016
14017
14018 /* Timekeeping seems to be a portability nightmare.  I think everyone
14019    has ftime(), but I'm really not sure, so I'm including some ifdefs
14020    to use other calls if you don't.  Clocks will be less accurate if
14021    you have neither ftime nor gettimeofday.
14022 */
14023
14024 /* VS 2008 requires the #include outside of the function */
14025 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14026 #include <sys/timeb.h>
14027 #endif
14028
14029 /* Get the current time as a TimeMark */
14030 void
14031 GetTimeMark(tm)
14032      TimeMark *tm;
14033 {
14034 #if HAVE_GETTIMEOFDAY
14035
14036     struct timeval timeVal;
14037     struct timezone timeZone;
14038
14039     gettimeofday(&timeVal, &timeZone);
14040     tm->sec = (long) timeVal.tv_sec; 
14041     tm->ms = (int) (timeVal.tv_usec / 1000L);
14042
14043 #else /*!HAVE_GETTIMEOFDAY*/
14044 #if HAVE_FTIME
14045
14046 // include <sys/timeb.h> / moved to just above start of function
14047     struct timeb timeB;
14048
14049     ftime(&timeB);
14050     tm->sec = (long) timeB.time;
14051     tm->ms = (int) timeB.millitm;
14052
14053 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14054     tm->sec = (long) time(NULL);
14055     tm->ms = 0;
14056 #endif
14057 #endif
14058 }
14059
14060 /* Return the difference in milliseconds between two
14061    time marks.  We assume the difference will fit in a long!
14062 */
14063 long
14064 SubtractTimeMarks(tm2, tm1)
14065      TimeMark *tm2, *tm1;
14066 {
14067     return 1000L*(tm2->sec - tm1->sec) +
14068            (long) (tm2->ms - tm1->ms);
14069 }
14070
14071
14072 /*
14073  * Code to manage the game clocks.
14074  *
14075  * In tournament play, black starts the clock and then white makes a move.
14076  * We give the human user a slight advantage if he is playing white---the
14077  * clocks don't run until he makes his first move, so it takes zero time.
14078  * Also, we don't account for network lag, so we could get out of sync
14079  * with GNU Chess's clock -- but then, referees are always right.  
14080  */
14081
14082 static TimeMark tickStartTM;
14083 static long intendedTickLength;
14084
14085 long
14086 NextTickLength(timeRemaining)
14087      long timeRemaining;
14088 {
14089     long nominalTickLength, nextTickLength;
14090
14091     if (timeRemaining > 0L && timeRemaining <= 10000L)
14092       nominalTickLength = 100L;
14093     else
14094       nominalTickLength = 1000L;
14095     nextTickLength = timeRemaining % nominalTickLength;
14096     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14097
14098     return nextTickLength;
14099 }
14100
14101 /* Adjust clock one minute up or down */
14102 void
14103 AdjustClock(Boolean which, int dir)
14104 {
14105     if(which) blackTimeRemaining += 60000*dir;
14106     else      whiteTimeRemaining += 60000*dir;
14107     DisplayBothClocks();
14108 }
14109
14110 /* Stop clocks and reset to a fresh time control */
14111 void
14112 ResetClocks() 
14113 {
14114     (void) StopClockTimer();
14115     if (appData.icsActive) {
14116         whiteTimeRemaining = blackTimeRemaining = 0;
14117     } else if (searchTime) {
14118         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14119         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14120     } else { /* [HGM] correct new time quote for time odds */
14121         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14122         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14123     }
14124     if (whiteFlag || blackFlag) {
14125         DisplayTitle("");
14126         whiteFlag = blackFlag = FALSE;
14127     }
14128     DisplayBothClocks();
14129 }
14130
14131 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14132
14133 /* Decrement running clock by amount of time that has passed */
14134 void
14135 DecrementClocks()
14136 {
14137     long timeRemaining;
14138     long lastTickLength, fudge;
14139     TimeMark now;
14140
14141     if (!appData.clockMode) return;
14142     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14143         
14144     GetTimeMark(&now);
14145
14146     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14147
14148     /* Fudge if we woke up a little too soon */
14149     fudge = intendedTickLength - lastTickLength;
14150     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14151
14152     if (WhiteOnMove(forwardMostMove)) {
14153         if(whiteNPS >= 0) lastTickLength = 0;
14154         timeRemaining = whiteTimeRemaining -= lastTickLength;
14155         DisplayWhiteClock(whiteTimeRemaining - fudge,
14156                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14157     } else {
14158         if(blackNPS >= 0) lastTickLength = 0;
14159         timeRemaining = blackTimeRemaining -= lastTickLength;
14160         DisplayBlackClock(blackTimeRemaining - fudge,
14161                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14162     }
14163
14164     if (CheckFlags()) return;
14165         
14166     tickStartTM = now;
14167     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14168     StartClockTimer(intendedTickLength);
14169
14170     /* if the time remaining has fallen below the alarm threshold, sound the
14171      * alarm. if the alarm has sounded and (due to a takeback or time control
14172      * with increment) the time remaining has increased to a level above the
14173      * threshold, reset the alarm so it can sound again. 
14174      */
14175     
14176     if (appData.icsActive && appData.icsAlarm) {
14177
14178         /* make sure we are dealing with the user's clock */
14179         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14180                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14181            )) return;
14182
14183         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14184             alarmSounded = FALSE;
14185         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14186             PlayAlarmSound();
14187             alarmSounded = TRUE;
14188         }
14189     }
14190 }
14191
14192
14193 /* A player has just moved, so stop the previously running
14194    clock and (if in clock mode) start the other one.
14195    We redisplay both clocks in case we're in ICS mode, because
14196    ICS gives us an update to both clocks after every move.
14197    Note that this routine is called *after* forwardMostMove
14198    is updated, so the last fractional tick must be subtracted
14199    from the color that is *not* on move now.
14200 */
14201 void
14202 SwitchClocks(int newMoveNr)
14203 {
14204     long lastTickLength;
14205     TimeMark now;
14206     int flagged = FALSE;
14207
14208     GetTimeMark(&now);
14209
14210     if (StopClockTimer() && appData.clockMode) {
14211         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14212         if (!WhiteOnMove(forwardMostMove)) {
14213             if(blackNPS >= 0) lastTickLength = 0;
14214             blackTimeRemaining -= lastTickLength;
14215            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14216 //         if(pvInfoList[forwardMostMove-1].time == -1)
14217                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14218                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14219         } else {
14220            if(whiteNPS >= 0) lastTickLength = 0;
14221            whiteTimeRemaining -= lastTickLength;
14222            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14223 //         if(pvInfoList[forwardMostMove-1].time == -1)
14224                  pvInfoList[forwardMostMove-1].time = 
14225                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14226         }
14227         flagged = CheckFlags();
14228     }
14229     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14230     CheckTimeControl();
14231
14232     if (flagged || !appData.clockMode) return;
14233
14234     switch (gameMode) {
14235       case MachinePlaysBlack:
14236       case MachinePlaysWhite:
14237       case BeginningOfGame:
14238         if (pausing) return;
14239         break;
14240
14241       case EditGame:
14242       case PlayFromGameFile:
14243       case IcsExamining:
14244         return;
14245
14246       default:
14247         break;
14248     }
14249
14250     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14251         if(WhiteOnMove(forwardMostMove))
14252              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14253         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14254     }
14255
14256     tickStartTM = now;
14257     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14258       whiteTimeRemaining : blackTimeRemaining);
14259     StartClockTimer(intendedTickLength);
14260 }
14261         
14262
14263 /* Stop both clocks */
14264 void
14265 StopClocks()
14266 {       
14267     long lastTickLength;
14268     TimeMark now;
14269
14270     if (!StopClockTimer()) return;
14271     if (!appData.clockMode) return;
14272
14273     GetTimeMark(&now);
14274
14275     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14276     if (WhiteOnMove(forwardMostMove)) {
14277         if(whiteNPS >= 0) lastTickLength = 0;
14278         whiteTimeRemaining -= lastTickLength;
14279         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14280     } else {
14281         if(blackNPS >= 0) lastTickLength = 0;
14282         blackTimeRemaining -= lastTickLength;
14283         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14284     }
14285     CheckFlags();
14286 }
14287         
14288 /* Start clock of player on move.  Time may have been reset, so
14289    if clock is already running, stop and restart it. */
14290 void
14291 StartClocks()
14292 {
14293     (void) StopClockTimer(); /* in case it was running already */
14294     DisplayBothClocks();
14295     if (CheckFlags()) return;
14296
14297     if (!appData.clockMode) return;
14298     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14299
14300     GetTimeMark(&tickStartTM);
14301     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14302       whiteTimeRemaining : blackTimeRemaining);
14303
14304    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14305     whiteNPS = blackNPS = -1; 
14306     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14307        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14308         whiteNPS = first.nps;
14309     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14310        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14311         blackNPS = first.nps;
14312     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14313         whiteNPS = second.nps;
14314     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14315         blackNPS = second.nps;
14316     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14317
14318     StartClockTimer(intendedTickLength);
14319 }
14320
14321 char *
14322 TimeString(ms)
14323      long ms;
14324 {
14325     long second, minute, hour, day;
14326     char *sign = "";
14327     static char buf[32];
14328     
14329     if (ms > 0 && ms <= 9900) {
14330       /* convert milliseconds to tenths, rounding up */
14331       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14332
14333       sprintf(buf, " %03.1f ", tenths/10.0);
14334       return buf;
14335     }
14336
14337     /* convert milliseconds to seconds, rounding up */
14338     /* use floating point to avoid strangeness of integer division
14339        with negative dividends on many machines */
14340     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14341
14342     if (second < 0) {
14343         sign = "-";
14344         second = -second;
14345     }
14346     
14347     day = second / (60 * 60 * 24);
14348     second = second % (60 * 60 * 24);
14349     hour = second / (60 * 60);
14350     second = second % (60 * 60);
14351     minute = second / 60;
14352     second = second % 60;
14353     
14354     if (day > 0)
14355       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14356               sign, day, hour, minute, second);
14357     else if (hour > 0)
14358       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14359     else
14360       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14361     
14362     return buf;
14363 }
14364
14365
14366 /*
14367  * This is necessary because some C libraries aren't ANSI C compliant yet.
14368  */
14369 char *
14370 StrStr(string, match)
14371      char *string, *match;
14372 {
14373     int i, length;
14374     
14375     length = strlen(match);
14376     
14377     for (i = strlen(string) - length; i >= 0; i--, string++)
14378       if (!strncmp(match, string, length))
14379         return string;
14380     
14381     return NULL;
14382 }
14383
14384 char *
14385 StrCaseStr(string, match)
14386      char *string, *match;
14387 {
14388     int i, j, length;
14389     
14390     length = strlen(match);
14391     
14392     for (i = strlen(string) - length; i >= 0; i--, string++) {
14393         for (j = 0; j < length; j++) {
14394             if (ToLower(match[j]) != ToLower(string[j]))
14395               break;
14396         }
14397         if (j == length) return string;
14398     }
14399
14400     return NULL;
14401 }
14402
14403 #ifndef _amigados
14404 int
14405 StrCaseCmp(s1, s2)
14406      char *s1, *s2;
14407 {
14408     char c1, c2;
14409     
14410     for (;;) {
14411         c1 = ToLower(*s1++);
14412         c2 = ToLower(*s2++);
14413         if (c1 > c2) return 1;
14414         if (c1 < c2) return -1;
14415         if (c1 == NULLCHAR) return 0;
14416     }
14417 }
14418
14419
14420 int
14421 ToLower(c)
14422      int c;
14423 {
14424     return isupper(c) ? tolower(c) : c;
14425 }
14426
14427
14428 int
14429 ToUpper(c)
14430      int c;
14431 {
14432     return islower(c) ? toupper(c) : c;
14433 }
14434 #endif /* !_amigados    */
14435
14436 char *
14437 StrSave(s)
14438      char *s;
14439 {
14440     char *ret;
14441
14442     if ((ret = (char *) malloc(strlen(s) + 1))) {
14443         strcpy(ret, s);
14444     }
14445     return ret;
14446 }
14447
14448 char *
14449 StrSavePtr(s, savePtr)
14450      char *s, **savePtr;
14451 {
14452     if (*savePtr) {
14453         free(*savePtr);
14454     }
14455     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14456         strcpy(*savePtr, s);
14457     }
14458     return(*savePtr);
14459 }
14460
14461 char *
14462 PGNDate()
14463 {
14464     time_t clock;
14465     struct tm *tm;
14466     char buf[MSG_SIZ];
14467
14468     clock = time((time_t *)NULL);
14469     tm = localtime(&clock);
14470     sprintf(buf, "%04d.%02d.%02d",
14471             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14472     return StrSave(buf);
14473 }
14474
14475
14476 char *
14477 PositionToFEN(move, overrideCastling)
14478      int move;
14479      char *overrideCastling;
14480 {
14481     int i, j, fromX, fromY, toX, toY;
14482     int whiteToPlay;
14483     char buf[128];
14484     char *p, *q;
14485     int emptycount;
14486     ChessSquare piece;
14487
14488     whiteToPlay = (gameMode == EditPosition) ?
14489       !blackPlaysFirst : (move % 2 == 0);
14490     p = buf;
14491
14492     /* Piece placement data */
14493     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14494         emptycount = 0;
14495         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14496             if (boards[move][i][j] == EmptySquare) {
14497                 emptycount++;
14498             } else { ChessSquare piece = boards[move][i][j];
14499                 if (emptycount > 0) {
14500                     if(emptycount<10) /* [HGM] can be >= 10 */
14501                         *p++ = '0' + emptycount;
14502                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14503                     emptycount = 0;
14504                 }
14505                 if(PieceToChar(piece) == '+') {
14506                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14507                     *p++ = '+';
14508                     piece = (ChessSquare)(DEMOTED piece);
14509                 } 
14510                 *p++ = PieceToChar(piece);
14511                 if(p[-1] == '~') {
14512                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14513                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14514                     *p++ = '~';
14515                 }
14516             }
14517         }
14518         if (emptycount > 0) {
14519             if(emptycount<10) /* [HGM] can be >= 10 */
14520                 *p++ = '0' + emptycount;
14521             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14522             emptycount = 0;
14523         }
14524         *p++ = '/';
14525     }
14526     *(p - 1) = ' ';
14527
14528     /* [HGM] print Crazyhouse or Shogi holdings */
14529     if( gameInfo.holdingsWidth ) {
14530         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14531         q = p;
14532         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14533             piece = boards[move][i][BOARD_WIDTH-1];
14534             if( piece != EmptySquare )
14535               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14536                   *p++ = PieceToChar(piece);
14537         }
14538         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14539             piece = boards[move][BOARD_HEIGHT-i-1][0];
14540             if( piece != EmptySquare )
14541               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14542                   *p++ = PieceToChar(piece);
14543         }
14544
14545         if( q == p ) *p++ = '-';
14546         *p++ = ']';
14547         *p++ = ' ';
14548     }
14549
14550     /* Active color */
14551     *p++ = whiteToPlay ? 'w' : 'b';
14552     *p++ = ' ';
14553
14554   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14555     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14556   } else {
14557   if(nrCastlingRights) {
14558      q = p;
14559      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14560        /* [HGM] write directly from rights */
14561            if(boards[move][CASTLING][2] != NoRights &&
14562               boards[move][CASTLING][0] != NoRights   )
14563                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14564            if(boards[move][CASTLING][2] != NoRights &&
14565               boards[move][CASTLING][1] != NoRights   )
14566                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14567            if(boards[move][CASTLING][5] != NoRights &&
14568               boards[move][CASTLING][3] != NoRights   )
14569                 *p++ = boards[move][CASTLING][3] + AAA;
14570            if(boards[move][CASTLING][5] != NoRights &&
14571               boards[move][CASTLING][4] != NoRights   )
14572                 *p++ = boards[move][CASTLING][4] + AAA;
14573      } else {
14574
14575         /* [HGM] write true castling rights */
14576         if( nrCastlingRights == 6 ) {
14577             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14578                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14579             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14580                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14581             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14582                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14583             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14584                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14585         }
14586      }
14587      if (q == p) *p++ = '-'; /* No castling rights */
14588      *p++ = ' ';
14589   }
14590
14591   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14592      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14593     /* En passant target square */
14594     if (move > backwardMostMove) {
14595         fromX = moveList[move - 1][0] - AAA;
14596         fromY = moveList[move - 1][1] - ONE;
14597         toX = moveList[move - 1][2] - AAA;
14598         toY = moveList[move - 1][3] - ONE;
14599         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14600             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14601             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14602             fromX == toX) {
14603             /* 2-square pawn move just happened */
14604             *p++ = toX + AAA;
14605             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14606         } else {
14607             *p++ = '-';
14608         }
14609     } else if(move == backwardMostMove) {
14610         // [HGM] perhaps we should always do it like this, and forget the above?
14611         if((signed char)boards[move][EP_STATUS] >= 0) {
14612             *p++ = boards[move][EP_STATUS] + AAA;
14613             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14614         } else {
14615             *p++ = '-';
14616         }
14617     } else {
14618         *p++ = '-';
14619     }
14620     *p++ = ' ';
14621   }
14622   }
14623
14624     /* [HGM] find reversible plies */
14625     {   int i = 0, j=move;
14626
14627         if (appData.debugMode) { int k;
14628             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14629             for(k=backwardMostMove; k<=forwardMostMove; k++)
14630                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14631
14632         }
14633
14634         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14635         if( j == backwardMostMove ) i += initialRulePlies;
14636         sprintf(p, "%d ", i);
14637         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14638     }
14639     /* Fullmove number */
14640     sprintf(p, "%d", (move / 2) + 1);
14641     
14642     return StrSave(buf);
14643 }
14644
14645 Boolean
14646 ParseFEN(board, blackPlaysFirst, fen)
14647     Board board;
14648      int *blackPlaysFirst;
14649      char *fen;
14650 {
14651     int i, j;
14652     char *p;
14653     int emptycount;
14654     ChessSquare piece;
14655
14656     p = fen;
14657
14658     /* [HGM] by default clear Crazyhouse holdings, if present */
14659     if(gameInfo.holdingsWidth) {
14660        for(i=0; i<BOARD_HEIGHT; i++) {
14661            board[i][0]             = EmptySquare; /* black holdings */
14662            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14663            board[i][1]             = (ChessSquare) 0; /* black counts */
14664            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14665        }
14666     }
14667
14668     /* Piece placement data */
14669     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14670         j = 0;
14671         for (;;) {
14672             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14673                 if (*p == '/') p++;
14674                 emptycount = gameInfo.boardWidth - j;
14675                 while (emptycount--)
14676                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14677                 break;
14678 #if(BOARD_FILES >= 10)
14679             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14680                 p++; emptycount=10;
14681                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14682                 while (emptycount--)
14683                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14684 #endif
14685             } else if (isdigit(*p)) {
14686                 emptycount = *p++ - '0';
14687                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14688                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14689                 while (emptycount--)
14690                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14691             } else if (*p == '+' || isalpha(*p)) {
14692                 if (j >= gameInfo.boardWidth) return FALSE;
14693                 if(*p=='+') {
14694                     piece = CharToPiece(*++p);
14695                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14696                     piece = (ChessSquare) (PROMOTED piece ); p++;
14697                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14698                 } else piece = CharToPiece(*p++);
14699
14700                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14701                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14702                     piece = (ChessSquare) (PROMOTED piece);
14703                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14704                     p++;
14705                 }
14706                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14707             } else {
14708                 return FALSE;
14709             }
14710         }
14711     }
14712     while (*p == '/' || *p == ' ') p++;
14713
14714     /* [HGM] look for Crazyhouse holdings here */
14715     while(*p==' ') p++;
14716     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14717         if(*p == '[') p++;
14718         if(*p == '-' ) *p++; /* empty holdings */ else {
14719             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14720             /* if we would allow FEN reading to set board size, we would   */
14721             /* have to add holdings and shift the board read so far here   */
14722             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14723                 *p++;
14724                 if((int) piece >= (int) BlackPawn ) {
14725                     i = (int)piece - (int)BlackPawn;
14726                     i = PieceToNumber((ChessSquare)i);
14727                     if( i >= gameInfo.holdingsSize ) return FALSE;
14728                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14729                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14730                 } else {
14731                     i = (int)piece - (int)WhitePawn;
14732                     i = PieceToNumber((ChessSquare)i);
14733                     if( i >= gameInfo.holdingsSize ) return FALSE;
14734                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14735                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14736                 }
14737             }
14738         }
14739         if(*p == ']') *p++;
14740     }
14741
14742     while(*p == ' ') p++;
14743
14744     /* Active color */
14745     switch (*p++) {
14746       case 'w':
14747         *blackPlaysFirst = FALSE;
14748         break;
14749       case 'b': 
14750         *blackPlaysFirst = TRUE;
14751         break;
14752       default:
14753         return FALSE;
14754     }
14755
14756     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14757     /* return the extra info in global variiables             */
14758
14759     /* set defaults in case FEN is incomplete */
14760     board[EP_STATUS] = EP_UNKNOWN;
14761     for(i=0; i<nrCastlingRights; i++ ) {
14762         board[CASTLING][i] =
14763             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14764     }   /* assume possible unless obviously impossible */
14765     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14766     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14767     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14768                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14769     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14770     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14771     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14772                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14773     FENrulePlies = 0;
14774
14775     while(*p==' ') p++;
14776     if(nrCastlingRights) {
14777       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14778           /* castling indicator present, so default becomes no castlings */
14779           for(i=0; i<nrCastlingRights; i++ ) {
14780                  board[CASTLING][i] = NoRights;
14781           }
14782       }
14783       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14784              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14785              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14786              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14787         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14788
14789         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14790             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14791             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14792         }
14793         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14794             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14795         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14796                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14797         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14798                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14799         switch(c) {
14800           case'K':
14801               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14802               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14803               board[CASTLING][2] = whiteKingFile;
14804               break;
14805           case'Q':
14806               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14807               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14808               board[CASTLING][2] = whiteKingFile;
14809               break;
14810           case'k':
14811               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14812               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14813               board[CASTLING][5] = blackKingFile;
14814               break;
14815           case'q':
14816               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14817               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14818               board[CASTLING][5] = blackKingFile;
14819           case '-':
14820               break;
14821           default: /* FRC castlings */
14822               if(c >= 'a') { /* black rights */
14823                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14824                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14825                   if(i == BOARD_RGHT) break;
14826                   board[CASTLING][5] = i;
14827                   c -= AAA;
14828                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14829                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14830                   if(c > i)
14831                       board[CASTLING][3] = c;
14832                   else
14833                       board[CASTLING][4] = c;
14834               } else { /* white rights */
14835                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14836                     if(board[0][i] == WhiteKing) break;
14837                   if(i == BOARD_RGHT) break;
14838                   board[CASTLING][2] = i;
14839                   c -= AAA - 'a' + 'A';
14840                   if(board[0][c] >= WhiteKing) break;
14841                   if(c > i)
14842                       board[CASTLING][0] = c;
14843                   else
14844                       board[CASTLING][1] = c;
14845               }
14846         }
14847       }
14848       for(i=0; i<nrCastlingRights; i++)
14849         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14850     if (appData.debugMode) {
14851         fprintf(debugFP, "FEN castling rights:");
14852         for(i=0; i<nrCastlingRights; i++)
14853         fprintf(debugFP, " %d", board[CASTLING][i]);
14854         fprintf(debugFP, "\n");
14855     }
14856
14857       while(*p==' ') p++;
14858     }
14859
14860     /* read e.p. field in games that know e.p. capture */
14861     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14862        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14863       if(*p=='-') {
14864         p++; board[EP_STATUS] = EP_NONE;
14865       } else {
14866          char c = *p++ - AAA;
14867
14868          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14869          if(*p >= '0' && *p <='9') *p++;
14870          board[EP_STATUS] = c;
14871       }
14872     }
14873
14874
14875     if(sscanf(p, "%d", &i) == 1) {
14876         FENrulePlies = i; /* 50-move ply counter */
14877         /* (The move number is still ignored)    */
14878     }
14879
14880     return TRUE;
14881 }
14882       
14883 void
14884 EditPositionPasteFEN(char *fen)
14885 {
14886   if (fen != NULL) {
14887     Board initial_position;
14888
14889     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14890       DisplayError(_("Bad FEN position in clipboard"), 0);
14891       return ;
14892     } else {
14893       int savedBlackPlaysFirst = blackPlaysFirst;
14894       EditPositionEvent();
14895       blackPlaysFirst = savedBlackPlaysFirst;
14896       CopyBoard(boards[0], initial_position);
14897       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14898       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14899       DisplayBothClocks();
14900       DrawPosition(FALSE, boards[currentMove]);
14901     }
14902   }
14903 }
14904
14905 static char cseq[12] = "\\   ";
14906
14907 Boolean set_cont_sequence(char *new_seq)
14908 {
14909     int len;
14910     Boolean ret;
14911
14912     // handle bad attempts to set the sequence
14913         if (!new_seq)
14914                 return 0; // acceptable error - no debug
14915
14916     len = strlen(new_seq);
14917     ret = (len > 0) && (len < sizeof(cseq));
14918     if (ret)
14919         strcpy(cseq, new_seq);
14920     else if (appData.debugMode)
14921         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14922     return ret;
14923 }
14924
14925 /*
14926     reformat a source message so words don't cross the width boundary.  internal
14927     newlines are not removed.  returns the wrapped size (no null character unless
14928     included in source message).  If dest is NULL, only calculate the size required
14929     for the dest buffer.  lp argument indicats line position upon entry, and it's
14930     passed back upon exit.
14931 */
14932 int wrap(char *dest, char *src, int count, int width, int *lp)
14933 {
14934     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14935
14936     cseq_len = strlen(cseq);
14937     old_line = line = *lp;
14938     ansi = len = clen = 0;
14939
14940     for (i=0; i < count; i++)
14941     {
14942         if (src[i] == '\033')
14943             ansi = 1;
14944
14945         // if we hit the width, back up
14946         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14947         {
14948             // store i & len in case the word is too long
14949             old_i = i, old_len = len;
14950
14951             // find the end of the last word
14952             while (i && src[i] != ' ' && src[i] != '\n')
14953             {
14954                 i--;
14955                 len--;
14956             }
14957
14958             // word too long?  restore i & len before splitting it
14959             if ((old_i-i+clen) >= width)
14960             {
14961                 i = old_i;
14962                 len = old_len;
14963             }
14964
14965             // extra space?
14966             if (i && src[i-1] == ' ')
14967                 len--;
14968
14969             if (src[i] != ' ' && src[i] != '\n')
14970             {
14971                 i--;
14972                 if (len)
14973                     len--;
14974             }
14975
14976             // now append the newline and continuation sequence
14977             if (dest)
14978                 dest[len] = '\n';
14979             len++;
14980             if (dest)
14981                 strncpy(dest+len, cseq, cseq_len);
14982             len += cseq_len;
14983             line = cseq_len;
14984             clen = cseq_len;
14985             continue;
14986         }
14987
14988         if (dest)
14989             dest[len] = src[i];
14990         len++;
14991         if (!ansi)
14992             line++;
14993         if (src[i] == '\n')
14994             line = 0;
14995         if (src[i] == 'm')
14996             ansi = 0;
14997     }
14998     if (dest && appData.debugMode)
14999     {
15000         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15001             count, width, line, len, *lp);
15002         show_bytes(debugFP, src, count);
15003         fprintf(debugFP, "\ndest: ");
15004         show_bytes(debugFP, dest, len);
15005         fprintf(debugFP, "\n");
15006     }
15007     *lp = dest ? line : old_line;
15008
15009     return len;
15010 }
15011
15012 // [HGM] vari: routines for shelving variations
15013
15014 void 
15015 PushTail(int firstMove, int lastMove)
15016 {
15017         int i, j, nrMoves = lastMove - firstMove;
15018
15019         if(appData.icsActive) { // only in local mode
15020                 forwardMostMove = currentMove; // mimic old ICS behavior
15021                 return;
15022         }
15023         if(storedGames >= MAX_VARIATIONS-1) return;
15024
15025         // push current tail of game on stack
15026         savedResult[storedGames] = gameInfo.result;
15027         savedDetails[storedGames] = gameInfo.resultDetails;
15028         gameInfo.resultDetails = NULL;
15029         savedFirst[storedGames] = firstMove;
15030         savedLast [storedGames] = lastMove;
15031         savedFramePtr[storedGames] = framePtr;
15032         framePtr -= nrMoves; // reserve space for the boards
15033         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15034             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15035             for(j=0; j<MOVE_LEN; j++)
15036                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15037             for(j=0; j<2*MOVE_LEN; j++)
15038                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15039             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15040             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15041             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15042             pvInfoList[firstMove+i-1].depth = 0;
15043             commentList[framePtr+i] = commentList[firstMove+i];
15044             commentList[firstMove+i] = NULL;
15045         }
15046
15047         storedGames++;
15048         forwardMostMove = firstMove; // truncate game so we can start variation
15049         if(storedGames == 1) GreyRevert(FALSE);
15050 }
15051
15052 Boolean
15053 PopTail(Boolean annotate)
15054 {
15055         int i, j, nrMoves;
15056         char buf[8000], moveBuf[20];
15057
15058         if(appData.icsActive) return FALSE; // only in local mode
15059         if(!storedGames) return FALSE; // sanity
15060         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15061
15062         storedGames--;
15063         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15064         nrMoves = savedLast[storedGames] - currentMove;
15065         if(annotate) {
15066                 int cnt = 10;
15067                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15068                 else strcpy(buf, "(");
15069                 for(i=currentMove; i<forwardMostMove; i++) {
15070                         if(WhiteOnMove(i))
15071                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15072                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15073                         strcat(buf, moveBuf);
15074                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15075                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15076                 }
15077                 strcat(buf, ")");
15078         }
15079         for(i=1; i<=nrMoves; i++) { // copy last variation back
15080             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15081             for(j=0; j<MOVE_LEN; j++)
15082                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15083             for(j=0; j<2*MOVE_LEN; j++)
15084                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15085             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15086             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15087             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15088             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15089             commentList[currentMove+i] = commentList[framePtr+i];
15090             commentList[framePtr+i] = NULL;
15091         }
15092         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15093         framePtr = savedFramePtr[storedGames];
15094         gameInfo.result = savedResult[storedGames];
15095         if(gameInfo.resultDetails != NULL) {
15096             free(gameInfo.resultDetails);
15097       }
15098         gameInfo.resultDetails = savedDetails[storedGames];
15099         forwardMostMove = currentMove + nrMoves;
15100         if(storedGames == 0) GreyRevert(TRUE);
15101         return TRUE;
15102 }
15103
15104 void 
15105 CleanupTail()
15106 {       // remove all shelved variations
15107         int i;
15108         for(i=0; i<storedGames; i++) {
15109             if(savedDetails[i])
15110                 free(savedDetails[i]);
15111             savedDetails[i] = NULL;
15112         }
15113         for(i=framePtr; i<MAX_MOVES; i++) {
15114                 if(commentList[i]) free(commentList[i]);
15115                 commentList[i] = NULL;
15116         }
15117         framePtr = MAX_MOVES-1;
15118         storedGames = 0;
15119 }
15120
15121 void
15122 LoadVariation(int index, char *text)
15123 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15124         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15125         int level = 0, move;
15126
15127         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15128         // first find outermost bracketing variation
15129         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15130             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15131                 if(*p == '{') wait = '}'; else
15132                 if(*p == '[') wait = ']'; else
15133                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15134                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15135             }
15136             if(*p == wait) wait = NULLCHAR; // closing ]} found
15137             p++;
15138         }
15139         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15140         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15141         end[1] = NULLCHAR; // clip off comment beyond variation
15142         ToNrEvent(currentMove-1);
15143         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15144         // kludge: use ParsePV() to append variation to game
15145         move = currentMove;
15146         ParsePV(start, TRUE);
15147         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15148         ClearPremoveHighlights();
15149         CommentPopDown();
15150         ToNrEvent(currentMove+1);
15151 }