4abcc4a42e04535dbd2a7dc8fa5092f6b3683217
[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)* log(tc)/log(100.) + 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)* log((double)i)/log(100.) + 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                     } else
2546                     if(!suppressKibitz) // [HGM] kibitz
2547                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2548                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2549                         int nrDigit = 0, nrAlph = 0, j;
2550                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2551                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2552                         parse[parse_pos] = NULLCHAR;
2553                         // try to be smart: if it does not look like search info, it should go to
2554                         // ICS interaction window after all, not to engine-output window.
2555                         for(j=0; j<parse_pos; j++) { // count letters and digits
2556                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2557                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2558                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2559                         }
2560                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2561                             int depth=0; float score;
2562                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2563                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2564                                 pvInfoList[forwardMostMove-1].depth = depth;
2565                                 pvInfoList[forwardMostMove-1].score = 100*score;
2566                             }
2567                             OutputKibitz(suppressKibitz, parse);
2568                             next_out = i+1; // [HGM] suppress printing in ICS window
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                     }
2575                     started = STARTED_NONE;
2576                 } else {
2577                     /* Don't match patterns against characters in comment */
2578                     i++;
2579                     continue;
2580                 }
2581             }
2582             if (started == STARTED_CHATTER) {
2583                 if (buf[i] != '\n') {
2584                     /* Don't match patterns against characters in chatter */
2585                     i++;
2586                     continue;
2587                 }
2588                 started = STARTED_NONE;
2589             }
2590
2591             /* Kludge to deal with rcmd protocol */
2592             if (firstTime && looking_at(buf, &i, "\001*")) {
2593                 DisplayFatalError(&buf[1], 0, 1);
2594                 continue;
2595             } else {
2596                 firstTime = FALSE;
2597             }
2598
2599             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2600                 ics_type = ICS_ICC;
2601                 ics_prefix = "/";
2602                 if (appData.debugMode)
2603                   fprintf(debugFP, "ics_type %d\n", ics_type);
2604                 continue;
2605             }
2606             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2607                 ics_type = ICS_FICS;
2608                 ics_prefix = "$";
2609                 if (appData.debugMode)
2610                   fprintf(debugFP, "ics_type %d\n", ics_type);
2611                 continue;
2612             }
2613             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2614                 ics_type = ICS_CHESSNET;
2615                 ics_prefix = "/";
2616                 if (appData.debugMode)
2617                   fprintf(debugFP, "ics_type %d\n", ics_type);
2618                 continue;
2619             }
2620
2621             if (!loggedOn &&
2622                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2623                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2624                  looking_at(buf, &i, "will be \"*\""))) {
2625               strcpy(ics_handle, star_match[0]);
2626               continue;
2627             }
2628
2629             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2630               char buf[MSG_SIZ];
2631               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2632               DisplayIcsInteractionTitle(buf);
2633               have_set_title = TRUE;
2634             }
2635
2636             /* skip finger notes */
2637             if (started == STARTED_NONE &&
2638                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2639                  (buf[i] == '1' && buf[i+1] == '0')) &&
2640                 buf[i+2] == ':' && buf[i+3] == ' ') {
2641               started = STARTED_CHATTER;
2642               i += 3;
2643               continue;
2644             }
2645
2646             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2647             if(appData.seekGraph) {
2648                 if(soughtPending && MatchSoughtLine(buf+i)) {
2649                     i = strstr(buf+i, "rated") - buf;
2650                     next_out = leftover_start = i;
2651                     started = STARTED_CHATTER;
2652                     suppressKibitz = TRUE;
2653                     continue;
2654                 }
2655                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2656                         && looking_at(buf, &i, "* ads displayed")) {
2657                     soughtPending = FALSE;
2658                     seekGraphUp = TRUE;
2659                     DrawSeekGraph();
2660                     continue;
2661                 }
2662                 if(appData.autoRefresh) {
2663                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2664                         int s = (ics_type == ICS_ICC); // ICC format differs
2665                         if(seekGraphUp)
2666                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2667                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2668                         looking_at(buf, &i, "*% "); // eat prompt
2669                         next_out = i; // suppress
2670                         continue;
2671                     }
2672                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2673                         char *p = star_match[0];
2674                         while(*p) {
2675                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2676                             while(*p && *p++ != ' '); // next
2677                         }
2678                         looking_at(buf, &i, "*% "); // eat prompt
2679                         next_out = i;
2680                         continue;
2681                     }
2682                 }
2683             }
2684
2685             /* skip formula vars */
2686             if (started == STARTED_NONE &&
2687                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2688               started = STARTED_CHATTER;
2689               i += 3;
2690               continue;
2691             }
2692
2693             oldi = i;
2694             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2695             if (appData.autoKibitz && started == STARTED_NONE && 
2696                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2697                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2698                 if(looking_at(buf, &i, "* kibitzes: ") &&
2699                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2700                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2701                         suppressKibitz = TRUE;
2702                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2703                                 && (gameMode == IcsPlayingWhite)) ||
2704                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2705                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2706                             started = STARTED_CHATTER; // own kibitz we simply discard
2707                         else {
2708                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2709                             parse_pos = 0; parse[0] = NULLCHAR;
2710                             savingComment = TRUE;
2711                             suppressKibitz = gameMode != IcsObserving ? 2 :
2712                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2713                         } 
2714                         continue;
2715                 } else
2716                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2717                     // suppress the acknowledgements of our own autoKibitz
2718                     char *p;
2719                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2720                     SendToPlayer(star_match[0], strlen(star_match[0]));
2721                     looking_at(buf, &i, "*% "); // eat prompt
2722                     next_out = i;
2723                 }
2724             } // [HGM] kibitz: end of patch
2725
2726 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2727
2728             // [HGM] chat: intercept tells by users for which we have an open chat window
2729             channel = -1;
2730             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2731                                            looking_at(buf, &i, "* whispers:") ||
2732                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2733                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2734                 int p;
2735                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2736                 chattingPartner = -1;
2737
2738                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2739                 for(p=0; p<MAX_CHAT; p++) {
2740                     if(channel == atoi(chatPartner[p])) {
2741                     talker[0] = '['; strcat(talker, "] ");
2742                     chattingPartner = p; break;
2743                     }
2744                 } else
2745                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2746                 for(p=0; p<MAX_CHAT; p++) {
2747                     if(!strcmp("WHISPER", chatPartner[p])) {
2748                         talker[0] = '['; strcat(talker, "] ");
2749                         chattingPartner = p; break;
2750                     }
2751                 }
2752                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2753                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2754                     talker[0] = 0;
2755                     chattingPartner = p; break;
2756                 }
2757                 if(chattingPartner<0) i = oldi; else {
2758                     started = STARTED_COMMENT;
2759                     parse_pos = 0; parse[0] = NULLCHAR;
2760                     savingComment = 3 + chattingPartner; // counts as TRUE
2761                     suppressKibitz = TRUE;
2762                 }
2763             } // [HGM] chat: end of patch
2764
2765             if (appData.zippyTalk || appData.zippyPlay) {
2766                 /* [DM] Backup address for color zippy lines */
2767                 backup = i;
2768 #if ZIPPY
2769        #ifdef WIN32
2770                if (loggedOn == TRUE)
2771                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2772                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2773        #else
2774                 if (ZippyControl(buf, &i) ||
2775                     ZippyConverse(buf, &i) ||
2776                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2777                       loggedOn = TRUE;
2778                       if (!appData.colorize) continue;
2779                 }
2780        #endif
2781 #endif
2782             } // [DM] 'else { ' deleted
2783                 if (
2784                     /* Regular tells and says */
2785                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2786                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2787                     looking_at(buf, &i, "* says: ") ||
2788                     /* Don't color "message" or "messages" output */
2789                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2790                     looking_at(buf, &i, "*. * at *:*: ") ||
2791                     looking_at(buf, &i, "--* (*:*): ") ||
2792                     /* Message notifications (same color as tells) */
2793                     looking_at(buf, &i, "* has left a message ") ||
2794                     looking_at(buf, &i, "* just sent you a message:\n") ||
2795                     /* Whispers and kibitzes */
2796                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2797                     looking_at(buf, &i, "* kibitzes: ") ||
2798                     /* Channel tells */
2799                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2800
2801                   if (tkind == 1 && strchr(star_match[0], ':')) {
2802                       /* Avoid "tells you:" spoofs in channels */
2803                      tkind = 3;
2804                   }
2805                   if (star_match[0][0] == NULLCHAR ||
2806                       strchr(star_match[0], ' ') ||
2807                       (tkind == 3 && strchr(star_match[1], ' '))) {
2808                     /* Reject bogus matches */
2809                     i = oldi;
2810                   } else {
2811                     if (appData.colorize) {
2812                       if (oldi > next_out) {
2813                         SendToPlayer(&buf[next_out], oldi - next_out);
2814                         next_out = oldi;
2815                       }
2816                       switch (tkind) {
2817                       case 1:
2818                         Colorize(ColorTell, FALSE);
2819                         curColor = ColorTell;
2820                         break;
2821                       case 2:
2822                         Colorize(ColorKibitz, FALSE);
2823                         curColor = ColorKibitz;
2824                         break;
2825                       case 3:
2826                         p = strrchr(star_match[1], '(');
2827                         if (p == NULL) {
2828                           p = star_match[1];
2829                         } else {
2830                           p++;
2831                         }
2832                         if (atoi(p) == 1) {
2833                           Colorize(ColorChannel1, FALSE);
2834                           curColor = ColorChannel1;
2835                         } else {
2836                           Colorize(ColorChannel, FALSE);
2837                           curColor = ColorChannel;
2838                         }
2839                         break;
2840                       case 5:
2841                         curColor = ColorNormal;
2842                         break;
2843                       }
2844                     }
2845                     if (started == STARTED_NONE && appData.autoComment &&
2846                         (gameMode == IcsObserving ||
2847                          gameMode == IcsPlayingWhite ||
2848                          gameMode == IcsPlayingBlack)) {
2849                       parse_pos = i - oldi;
2850                       memcpy(parse, &buf[oldi], parse_pos);
2851                       parse[parse_pos] = NULLCHAR;
2852                       started = STARTED_COMMENT;
2853                       savingComment = TRUE;
2854                     } else {
2855                       started = STARTED_CHATTER;
2856                       savingComment = FALSE;
2857                     }
2858                     loggedOn = TRUE;
2859                     continue;
2860                   }
2861                 }
2862
2863                 if (looking_at(buf, &i, "* s-shouts: ") ||
2864                     looking_at(buf, &i, "* c-shouts: ")) {
2865                     if (appData.colorize) {
2866                         if (oldi > next_out) {
2867                             SendToPlayer(&buf[next_out], oldi - next_out);
2868                             next_out = oldi;
2869                         }
2870                         Colorize(ColorSShout, FALSE);
2871                         curColor = ColorSShout;
2872                     }
2873                     loggedOn = TRUE;
2874                     started = STARTED_CHATTER;
2875                     continue;
2876                 }
2877
2878                 if (looking_at(buf, &i, "--->")) {
2879                     loggedOn = TRUE;
2880                     continue;
2881                 }
2882
2883                 if (looking_at(buf, &i, "* shouts: ") ||
2884                     looking_at(buf, &i, "--> ")) {
2885                     if (appData.colorize) {
2886                         if (oldi > next_out) {
2887                             SendToPlayer(&buf[next_out], oldi - next_out);
2888                             next_out = oldi;
2889                         }
2890                         Colorize(ColorShout, FALSE);
2891                         curColor = ColorShout;
2892                     }
2893                     loggedOn = TRUE;
2894                     started = STARTED_CHATTER;
2895                     continue;
2896                 }
2897
2898                 if (looking_at( buf, &i, "Challenge:")) {
2899                     if (appData.colorize) {
2900                         if (oldi > next_out) {
2901                             SendToPlayer(&buf[next_out], oldi - next_out);
2902                             next_out = oldi;
2903                         }
2904                         Colorize(ColorChallenge, FALSE);
2905                         curColor = ColorChallenge;
2906                     }
2907                     loggedOn = TRUE;
2908                     continue;
2909                 }
2910
2911                 if (looking_at(buf, &i, "* offers you") ||
2912                     looking_at(buf, &i, "* offers to be") ||
2913                     looking_at(buf, &i, "* would like to") ||
2914                     looking_at(buf, &i, "* requests to") ||
2915                     looking_at(buf, &i, "Your opponent offers") ||
2916                     looking_at(buf, &i, "Your opponent requests")) {
2917
2918                     if (appData.colorize) {
2919                         if (oldi > next_out) {
2920                             SendToPlayer(&buf[next_out], oldi - next_out);
2921                             next_out = oldi;
2922                         }
2923                         Colorize(ColorRequest, FALSE);
2924                         curColor = ColorRequest;
2925                     }
2926                     continue;
2927                 }
2928
2929                 if (looking_at(buf, &i, "* (*) seeking")) {
2930                     if (appData.colorize) {
2931                         if (oldi > next_out) {
2932                             SendToPlayer(&buf[next_out], oldi - next_out);
2933                             next_out = oldi;
2934                         }
2935                         Colorize(ColorSeek, FALSE);
2936                         curColor = ColorSeek;
2937                     }
2938                     continue;
2939             }
2940
2941             if (looking_at(buf, &i, "\\   ")) {
2942                 if (prevColor != ColorNormal) {
2943                     if (oldi > next_out) {
2944                         SendToPlayer(&buf[next_out], oldi - next_out);
2945                         next_out = oldi;
2946                     }
2947                     Colorize(prevColor, TRUE);
2948                     curColor = prevColor;
2949                 }
2950                 if (savingComment) {
2951                     parse_pos = i - oldi;
2952                     memcpy(parse, &buf[oldi], parse_pos);
2953                     parse[parse_pos] = NULLCHAR;
2954                     started = STARTED_COMMENT;
2955                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2956                         chattingPartner = savingComment - 3; // kludge to remember the box
2957                 } else {
2958                     started = STARTED_CHATTER;
2959                 }
2960                 continue;
2961             }
2962
2963             if (looking_at(buf, &i, "Black Strength :") ||
2964                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2965                 looking_at(buf, &i, "<10>") ||
2966                 looking_at(buf, &i, "#@#")) {
2967                 /* Wrong board style */
2968                 loggedOn = TRUE;
2969                 SendToICS(ics_prefix);
2970                 SendToICS("set style 12\n");
2971                 SendToICS(ics_prefix);
2972                 SendToICS("refresh\n");
2973                 continue;
2974             }
2975             
2976             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2977                 ICSInitScript();
2978                 have_sent_ICS_logon = 1;
2979                 continue;
2980             }
2981               
2982             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2983                 (looking_at(buf, &i, "\n<12> ") ||
2984                  looking_at(buf, &i, "<12> "))) {
2985                 loggedOn = TRUE;
2986                 if (oldi > next_out) {
2987                     SendToPlayer(&buf[next_out], oldi - next_out);
2988                 }
2989                 next_out = i;
2990                 started = STARTED_BOARD;
2991                 parse_pos = 0;
2992                 continue;
2993             }
2994
2995             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2996                 looking_at(buf, &i, "<b1> ")) {
2997                 if (oldi > next_out) {
2998                     SendToPlayer(&buf[next_out], oldi - next_out);
2999                 }
3000                 next_out = i;
3001                 started = STARTED_HOLDINGS;
3002                 parse_pos = 0;
3003                 continue;
3004             }
3005
3006             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3007                 loggedOn = TRUE;
3008                 /* Header for a move list -- first line */
3009
3010                 switch (ics_getting_history) {
3011                   case H_FALSE:
3012                     switch (gameMode) {
3013                       case IcsIdle:
3014                       case BeginningOfGame:
3015                         /* User typed "moves" or "oldmoves" while we
3016                            were idle.  Pretend we asked for these
3017                            moves and soak them up so user can step
3018                            through them and/or save them.
3019                            */
3020                         Reset(FALSE, TRUE);
3021                         gameMode = IcsObserving;
3022                         ModeHighlight();
3023                         ics_gamenum = -1;
3024                         ics_getting_history = H_GOT_UNREQ_HEADER;
3025                         break;
3026                       case EditGame: /*?*/
3027                       case EditPosition: /*?*/
3028                         /* Should above feature work in these modes too? */
3029                         /* For now it doesn't */
3030                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3031                         break;
3032                       default:
3033                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3034                         break;
3035                     }
3036                     break;
3037                   case H_REQUESTED:
3038                     /* Is this the right one? */
3039                     if (gameInfo.white && gameInfo.black &&
3040                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3041                         strcmp(gameInfo.black, star_match[2]) == 0) {
3042                         /* All is well */
3043                         ics_getting_history = H_GOT_REQ_HEADER;
3044                     }
3045                     break;
3046                   case H_GOT_REQ_HEADER:
3047                   case H_GOT_UNREQ_HEADER:
3048                   case H_GOT_UNWANTED_HEADER:
3049                   case H_GETTING_MOVES:
3050                     /* Should not happen */
3051                     DisplayError(_("Error gathering move list: two headers"), 0);
3052                     ics_getting_history = H_FALSE;
3053                     break;
3054                 }
3055
3056                 /* Save player ratings into gameInfo if needed */
3057                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3058                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3059                     (gameInfo.whiteRating == -1 ||
3060                      gameInfo.blackRating == -1)) {
3061
3062                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3063                     gameInfo.blackRating = string_to_rating(star_match[3]);
3064                     if (appData.debugMode)
3065                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3066                               gameInfo.whiteRating, gameInfo.blackRating);
3067                 }
3068                 continue;
3069             }
3070
3071             if (looking_at(buf, &i,
3072               "* * match, initial time: * minute*, increment: * second")) {
3073                 /* Header for a move list -- second line */
3074                 /* Initial board will follow if this is a wild game */
3075                 if (gameInfo.event != NULL) free(gameInfo.event);
3076                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3077                 gameInfo.event = StrSave(str);
3078                 /* [HGM] we switched variant. Translate boards if needed. */
3079                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3080                 continue;
3081             }
3082
3083             if (looking_at(buf, &i, "Move  ")) {
3084                 /* Beginning of a move list */
3085                 switch (ics_getting_history) {
3086                   case H_FALSE:
3087                     /* Normally should not happen */
3088                     /* Maybe user hit reset while we were parsing */
3089                     break;
3090                   case H_REQUESTED:
3091                     /* Happens if we are ignoring a move list that is not
3092                      * the one we just requested.  Common if the user
3093                      * tries to observe two games without turning off
3094                      * getMoveList */
3095                     break;
3096                   case H_GETTING_MOVES:
3097                     /* Should not happen */
3098                     DisplayError(_("Error gathering move list: nested"), 0);
3099                     ics_getting_history = H_FALSE;
3100                     break;
3101                   case H_GOT_REQ_HEADER:
3102                     ics_getting_history = H_GETTING_MOVES;
3103                     started = STARTED_MOVES;
3104                     parse_pos = 0;
3105                     if (oldi > next_out) {
3106                         SendToPlayer(&buf[next_out], oldi - next_out);
3107                     }
3108                     break;
3109                   case H_GOT_UNREQ_HEADER:
3110                     ics_getting_history = H_GETTING_MOVES;
3111                     started = STARTED_MOVES_NOHIDE;
3112                     parse_pos = 0;
3113                     break;
3114                   case H_GOT_UNWANTED_HEADER:
3115                     ics_getting_history = H_FALSE;
3116                     break;
3117                 }
3118                 continue;
3119             }                           
3120             
3121             if (looking_at(buf, &i, "% ") ||
3122                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3123                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3124                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3125                     soughtPending = FALSE;
3126                     seekGraphUp = TRUE;
3127                     DrawSeekGraph();
3128                 }
3129                 if(suppressKibitz) next_out = i;
3130                 savingComment = FALSE;
3131                 suppressKibitz = 0;
3132                 switch (started) {
3133                   case STARTED_MOVES:
3134                   case STARTED_MOVES_NOHIDE:
3135                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3136                     parse[parse_pos + i - oldi] = NULLCHAR;
3137                     ParseGameHistory(parse);
3138 #if ZIPPY
3139                     if (appData.zippyPlay && first.initDone) {
3140                         FeedMovesToProgram(&first, forwardMostMove);
3141                         if (gameMode == IcsPlayingWhite) {
3142                             if (WhiteOnMove(forwardMostMove)) {
3143                                 if (first.sendTime) {
3144                                   if (first.useColors) {
3145                                     SendToProgram("black\n", &first); 
3146                                   }
3147                                   SendTimeRemaining(&first, TRUE);
3148                                 }
3149                                 if (first.useColors) {
3150                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3151                                 }
3152                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3153                                 first.maybeThinking = TRUE;
3154                             } else {
3155                                 if (first.usePlayother) {
3156                                   if (first.sendTime) {
3157                                     SendTimeRemaining(&first, TRUE);
3158                                   }
3159                                   SendToProgram("playother\n", &first);
3160                                   firstMove = FALSE;
3161                                 } else {
3162                                   firstMove = TRUE;
3163                                 }
3164                             }
3165                         } else if (gameMode == IcsPlayingBlack) {
3166                             if (!WhiteOnMove(forwardMostMove)) {
3167                                 if (first.sendTime) {
3168                                   if (first.useColors) {
3169                                     SendToProgram("white\n", &first);
3170                                   }
3171                                   SendTimeRemaining(&first, FALSE);
3172                                 }
3173                                 if (first.useColors) {
3174                                   SendToProgram("black\n", &first);
3175                                 }
3176                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3177                                 first.maybeThinking = TRUE;
3178                             } else {
3179                                 if (first.usePlayother) {
3180                                   if (first.sendTime) {
3181                                     SendTimeRemaining(&first, FALSE);
3182                                   }
3183                                   SendToProgram("playother\n", &first);
3184                                   firstMove = FALSE;
3185                                 } else {
3186                                   firstMove = TRUE;
3187                                 }
3188                             }
3189                         }                       
3190                     }
3191 #endif
3192                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3193                         /* Moves came from oldmoves or moves command
3194                            while we weren't doing anything else.
3195                            */
3196                         currentMove = forwardMostMove;
3197                         ClearHighlights();/*!!could figure this out*/
3198                         flipView = appData.flipView;
3199                         DrawPosition(TRUE, boards[currentMove]);
3200                         DisplayBothClocks();
3201                         sprintf(str, "%s vs. %s",
3202                                 gameInfo.white, gameInfo.black);
3203                         DisplayTitle(str);
3204                         gameMode = IcsIdle;
3205                     } else {
3206                         /* Moves were history of an active game */
3207                         if (gameInfo.resultDetails != NULL) {
3208                             free(gameInfo.resultDetails);
3209                             gameInfo.resultDetails = NULL;
3210                         }
3211                     }
3212                     HistorySet(parseList, backwardMostMove,
3213                                forwardMostMove, currentMove-1);
3214                     DisplayMove(currentMove - 1);
3215                     if (started == STARTED_MOVES) next_out = i;
3216                     started = STARTED_NONE;
3217                     ics_getting_history = H_FALSE;
3218                     break;
3219
3220                   case STARTED_OBSERVE:
3221                     started = STARTED_NONE;
3222                     SendToICS(ics_prefix);
3223                     SendToICS("refresh\n");
3224                     break;
3225
3226                   default:
3227                     break;
3228                 }
3229                 if(bookHit) { // [HGM] book: simulate book reply
3230                     static char bookMove[MSG_SIZ]; // a bit generous?
3231
3232                     programStats.nodes = programStats.depth = programStats.time = 
3233                     programStats.score = programStats.got_only_move = 0;
3234                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3235
3236                     strcpy(bookMove, "move ");
3237                     strcat(bookMove, bookHit);
3238                     HandleMachineMove(bookMove, &first);
3239                 }
3240                 continue;
3241             }
3242             
3243             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3244                  started == STARTED_HOLDINGS ||
3245                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3246                 /* Accumulate characters in move list or board */
3247                 parse[parse_pos++] = buf[i];
3248             }
3249             
3250             /* Start of game messages.  Mostly we detect start of game
3251                when the first board image arrives.  On some versions
3252                of the ICS, though, we need to do a "refresh" after starting
3253                to observe in order to get the current board right away. */
3254             if (looking_at(buf, &i, "Adding game * to observation list")) {
3255                 started = STARTED_OBSERVE;
3256                 continue;
3257             }
3258
3259             /* Handle auto-observe */
3260             if (appData.autoObserve &&
3261                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3262                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3263                 char *player;
3264                 /* Choose the player that was highlighted, if any. */
3265                 if (star_match[0][0] == '\033' ||
3266                     star_match[1][0] != '\033') {
3267                     player = star_match[0];
3268                 } else {
3269                     player = star_match[2];
3270                 }
3271                 sprintf(str, "%sobserve %s\n",
3272                         ics_prefix, StripHighlightAndTitle(player));
3273                 SendToICS(str);
3274
3275                 /* Save ratings from notify string */
3276                 strcpy(player1Name, star_match[0]);
3277                 player1Rating = string_to_rating(star_match[1]);
3278                 strcpy(player2Name, star_match[2]);
3279                 player2Rating = string_to_rating(star_match[3]);
3280
3281                 if (appData.debugMode)
3282                   fprintf(debugFP, 
3283                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3284                           player1Name, player1Rating,
3285                           player2Name, player2Rating);
3286
3287                 continue;
3288             }
3289
3290             /* Deal with automatic examine mode after a game,
3291                and with IcsObserving -> IcsExamining transition */
3292             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3293                 looking_at(buf, &i, "has made you an examiner of game *")) {
3294
3295                 int gamenum = atoi(star_match[0]);
3296                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3297                     gamenum == ics_gamenum) {
3298                     /* We were already playing or observing this game;
3299                        no need to refetch history */
3300                     gameMode = IcsExamining;
3301                     if (pausing) {
3302                         pauseExamForwardMostMove = forwardMostMove;
3303                     } else if (currentMove < forwardMostMove) {
3304                         ForwardInner(forwardMostMove);
3305                     }
3306                 } else {
3307                     /* I don't think this case really can happen */
3308                     SendToICS(ics_prefix);
3309                     SendToICS("refresh\n");
3310                 }
3311                 continue;
3312             }    
3313             
3314             /* Error messages */
3315 //          if (ics_user_moved) {
3316             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3317                 if (looking_at(buf, &i, "Illegal move") ||
3318                     looking_at(buf, &i, "Not a legal move") ||
3319                     looking_at(buf, &i, "Your king is in check") ||
3320                     looking_at(buf, &i, "It isn't your turn") ||
3321                     looking_at(buf, &i, "It is not your move")) {
3322                     /* Illegal move */
3323                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3324                         currentMove = forwardMostMove-1;
3325                         DisplayMove(currentMove - 1); /* before DMError */
3326                         DrawPosition(FALSE, boards[currentMove]);
3327                         SwitchClocks(forwardMostMove-1); // [HGM] race
3328                         DisplayBothClocks();
3329                     }
3330                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3331                     ics_user_moved = 0;
3332                     continue;
3333                 }
3334             }
3335
3336             if (looking_at(buf, &i, "still have time") ||
3337                 looking_at(buf, &i, "not out of time") ||
3338                 looking_at(buf, &i, "either player is out of time") ||
3339                 looking_at(buf, &i, "has timeseal; checking")) {
3340                 /* We must have called his flag a little too soon */
3341                 whiteFlag = blackFlag = FALSE;
3342                 continue;
3343             }
3344
3345             if (looking_at(buf, &i, "added * seconds to") ||
3346                 looking_at(buf, &i, "seconds were added to")) {
3347                 /* Update the clocks */
3348                 SendToICS(ics_prefix);
3349                 SendToICS("refresh\n");
3350                 continue;
3351             }
3352
3353             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3354                 ics_clock_paused = TRUE;
3355                 StopClocks();
3356                 continue;
3357             }
3358
3359             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3360                 ics_clock_paused = FALSE;
3361                 StartClocks();
3362                 continue;
3363             }
3364
3365             /* Grab player ratings from the Creating: message.
3366                Note we have to check for the special case when
3367                the ICS inserts things like [white] or [black]. */
3368             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3369                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3370                 /* star_matches:
3371                    0    player 1 name (not necessarily white)
3372                    1    player 1 rating
3373                    2    empty, white, or black (IGNORED)
3374                    3    player 2 name (not necessarily black)
3375                    4    player 2 rating
3376                    
3377                    The names/ratings are sorted out when the game
3378                    actually starts (below).
3379                 */
3380                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3381                 player1Rating = string_to_rating(star_match[1]);
3382                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3383                 player2Rating = string_to_rating(star_match[4]);
3384
3385                 if (appData.debugMode)
3386                   fprintf(debugFP, 
3387                           "Ratings from 'Creating:' %s %d, %s %d\n",
3388                           player1Name, player1Rating,
3389                           player2Name, player2Rating);
3390
3391                 continue;
3392             }
3393             
3394             /* Improved generic start/end-of-game messages */
3395             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3396                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3397                 /* If tkind == 0: */
3398                 /* star_match[0] is the game number */
3399                 /*           [1] is the white player's name */
3400                 /*           [2] is the black player's name */
3401                 /* For end-of-game: */
3402                 /*           [3] is the reason for the game end */
3403                 /*           [4] is a PGN end game-token, preceded by " " */
3404                 /* For start-of-game: */
3405                 /*           [3] begins with "Creating" or "Continuing" */
3406                 /*           [4] is " *" or empty (don't care). */
3407                 int gamenum = atoi(star_match[0]);
3408                 char *whitename, *blackname, *why, *endtoken;
3409                 ChessMove endtype = (ChessMove) 0;
3410
3411                 if (tkind == 0) {
3412                   whitename = star_match[1];
3413                   blackname = star_match[2];
3414                   why = star_match[3];
3415                   endtoken = star_match[4];
3416                 } else {
3417                   whitename = star_match[1];
3418                   blackname = star_match[3];
3419                   why = star_match[5];
3420                   endtoken = star_match[6];
3421                 }
3422
3423                 /* Game start messages */
3424                 if (strncmp(why, "Creating ", 9) == 0 ||
3425                     strncmp(why, "Continuing ", 11) == 0) {
3426                     gs_gamenum = gamenum;
3427                     strcpy(gs_kind, strchr(why, ' ') + 1);
3428                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3429 #if ZIPPY
3430                     if (appData.zippyPlay) {
3431                         ZippyGameStart(whitename, blackname);
3432                     }
3433 #endif /*ZIPPY*/
3434                     continue;
3435                 }
3436
3437                 /* Game end messages */
3438                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3439                     ics_gamenum != gamenum) {
3440                     continue;
3441                 }
3442                 while (endtoken[0] == ' ') endtoken++;
3443                 switch (endtoken[0]) {
3444                   case '*':
3445                   default:
3446                     endtype = GameUnfinished;
3447                     break;
3448                   case '0':
3449                     endtype = BlackWins;
3450                     break;
3451                   case '1':
3452                     if (endtoken[1] == '/')
3453                       endtype = GameIsDrawn;
3454                     else
3455                       endtype = WhiteWins;
3456                     break;
3457                 }
3458                 GameEnds(endtype, why, GE_ICS);
3459 #if ZIPPY
3460                 if (appData.zippyPlay && first.initDone) {
3461                     ZippyGameEnd(endtype, why);
3462                     if (first.pr == NULL) {
3463                       /* Start the next process early so that we'll
3464                          be ready for the next challenge */
3465                       StartChessProgram(&first);
3466                     }
3467                     /* Send "new" early, in case this command takes
3468                        a long time to finish, so that we'll be ready
3469                        for the next challenge. */
3470                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3471                     Reset(TRUE, TRUE);
3472                 }
3473 #endif /*ZIPPY*/
3474                 continue;
3475             }
3476
3477             if (looking_at(buf, &i, "Removing game * from observation") ||
3478                 looking_at(buf, &i, "no longer observing game *") ||
3479                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3480                 if (gameMode == IcsObserving &&
3481                     atoi(star_match[0]) == ics_gamenum)
3482                   {
3483                       /* icsEngineAnalyze */
3484                       if (appData.icsEngineAnalyze) {
3485                             ExitAnalyzeMode();
3486                             ModeHighlight();
3487                       }
3488                       StopClocks();
3489                       gameMode = IcsIdle;
3490                       ics_gamenum = -1;
3491                       ics_user_moved = FALSE;
3492                   }
3493                 continue;
3494             }
3495
3496             if (looking_at(buf, &i, "no longer examining game *")) {
3497                 if (gameMode == IcsExamining &&
3498                     atoi(star_match[0]) == ics_gamenum)
3499                   {
3500                       gameMode = IcsIdle;
3501                       ics_gamenum = -1;
3502                       ics_user_moved = FALSE;
3503                   }
3504                 continue;
3505             }
3506
3507             /* Advance leftover_start past any newlines we find,
3508                so only partial lines can get reparsed */
3509             if (looking_at(buf, &i, "\n")) {
3510                 prevColor = curColor;
3511                 if (curColor != ColorNormal) {
3512                     if (oldi > next_out) {
3513                         SendToPlayer(&buf[next_out], oldi - next_out);
3514                         next_out = oldi;
3515                     }
3516                     Colorize(ColorNormal, FALSE);
3517                     curColor = ColorNormal;
3518                 }
3519                 if (started == STARTED_BOARD) {
3520                     started = STARTED_NONE;
3521                     parse[parse_pos] = NULLCHAR;
3522                     ParseBoard12(parse);
3523                     ics_user_moved = 0;
3524
3525                     /* Send premove here */
3526                     if (appData.premove) {
3527                       char str[MSG_SIZ];
3528                       if (currentMove == 0 &&
3529                           gameMode == IcsPlayingWhite &&
3530                           appData.premoveWhite) {
3531                         sprintf(str, "%s\n", appData.premoveWhiteText);
3532                         if (appData.debugMode)
3533                           fprintf(debugFP, "Sending premove:\n");
3534                         SendToICS(str);
3535                       } else if (currentMove == 1 &&
3536                                  gameMode == IcsPlayingBlack &&
3537                                  appData.premoveBlack) {
3538                         sprintf(str, "%s\n", appData.premoveBlackText);
3539                         if (appData.debugMode)
3540                           fprintf(debugFP, "Sending premove:\n");
3541                         SendToICS(str);
3542                       } else if (gotPremove) {
3543                         gotPremove = 0;
3544                         ClearPremoveHighlights();
3545                         if (appData.debugMode)
3546                           fprintf(debugFP, "Sending premove:\n");
3547                           UserMoveEvent(premoveFromX, premoveFromY, 
3548                                         premoveToX, premoveToY, 
3549                                         premovePromoChar);
3550                       }
3551                     }
3552
3553                     /* Usually suppress following prompt */
3554                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3555                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3556                         if (looking_at(buf, &i, "*% ")) {
3557                             savingComment = FALSE;
3558                             suppressKibitz = 0;
3559                         }
3560                     }
3561                     next_out = i;
3562                 } else if (started == STARTED_HOLDINGS) {
3563                     int gamenum;
3564                     char new_piece[MSG_SIZ];
3565                     started = STARTED_NONE;
3566                     parse[parse_pos] = NULLCHAR;
3567                     if (appData.debugMode)
3568                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3569                                                         parse, currentMove);
3570                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3571                         gamenum == ics_gamenum) {
3572                         if (gameInfo.variant == VariantNormal) {
3573                           /* [HGM] We seem to switch variant during a game!
3574                            * Presumably no holdings were displayed, so we have
3575                            * to move the position two files to the right to
3576                            * create room for them!
3577                            */
3578                           VariantClass newVariant;
3579                           switch(gameInfo.boardWidth) { // base guess on board width
3580                                 case 9:  newVariant = VariantShogi; break;
3581                                 case 10: newVariant = VariantGreat; break;
3582                                 default: newVariant = VariantCrazyhouse; break;
3583                           }
3584                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3585                           /* Get a move list just to see the header, which
3586                              will tell us whether this is really bug or zh */
3587                           if (ics_getting_history == H_FALSE) {
3588                             ics_getting_history = H_REQUESTED;
3589                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3590                             SendToICS(str);
3591                           }
3592                         }
3593                         new_piece[0] = NULLCHAR;
3594                         sscanf(parse, "game %d white [%s black [%s <- %s",
3595                                &gamenum, white_holding, black_holding,
3596                                new_piece);
3597                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3598                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3599                         /* [HGM] copy holdings to board holdings area */
3600                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3601                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3602                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3603 #if ZIPPY
3604                         if (appData.zippyPlay && first.initDone) {
3605                             ZippyHoldings(white_holding, black_holding,
3606                                           new_piece);
3607                         }
3608 #endif /*ZIPPY*/
3609                         if (tinyLayout || smallLayout) {
3610                             char wh[16], bh[16];
3611                             PackHolding(wh, white_holding);
3612                             PackHolding(bh, black_holding);
3613                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3614                                     gameInfo.white, gameInfo.black);
3615                         } else {
3616                             sprintf(str, "%s [%s] vs. %s [%s]",
3617                                     gameInfo.white, white_holding,
3618                                     gameInfo.black, black_holding);
3619                         }
3620
3621                         DrawPosition(FALSE, boards[currentMove]);
3622                         DisplayTitle(str);
3623                     }
3624                     /* Suppress following prompt */
3625                     if (looking_at(buf, &i, "*% ")) {
3626                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3627                         savingComment = FALSE;
3628                         suppressKibitz = 0;
3629                     }
3630                     next_out = i;
3631                 }
3632                 continue;
3633             }
3634
3635             i++;                /* skip unparsed character and loop back */
3636         }
3637         
3638         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3639 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3640 //          SendToPlayer(&buf[next_out], i - next_out);
3641             started != STARTED_HOLDINGS && leftover_start > next_out) {
3642             SendToPlayer(&buf[next_out], leftover_start - next_out);
3643             next_out = i;
3644         }
3645         
3646         leftover_len = buf_len - leftover_start;
3647         /* if buffer ends with something we couldn't parse,
3648            reparse it after appending the next read */
3649         
3650     } else if (count == 0) {
3651         RemoveInputSource(isr);
3652         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3653     } else {
3654         DisplayFatalError(_("Error reading from ICS"), error, 1);
3655     }
3656 }
3657
3658
3659 /* Board style 12 looks like this:
3660    
3661    <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
3662    
3663  * The "<12> " is stripped before it gets to this routine.  The two
3664  * trailing 0's (flip state and clock ticking) are later addition, and
3665  * some chess servers may not have them, or may have only the first.
3666  * Additional trailing fields may be added in the future.  
3667  */
3668
3669 #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"
3670
3671 #define RELATION_OBSERVING_PLAYED    0
3672 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3673 #define RELATION_PLAYING_MYMOVE      1
3674 #define RELATION_PLAYING_NOTMYMOVE  -1
3675 #define RELATION_EXAMINING           2
3676 #define RELATION_ISOLATED_BOARD     -3
3677 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3678
3679 void
3680 ParseBoard12(string)
3681      char *string;
3682
3683     GameMode newGameMode;
3684     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3685     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3686     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3687     char to_play, board_chars[200];
3688     char move_str[500], str[500], elapsed_time[500];
3689     char black[32], white[32];
3690     Board board;
3691     int prevMove = currentMove;
3692     int ticking = 2;
3693     ChessMove moveType;
3694     int fromX, fromY, toX, toY;
3695     char promoChar;
3696     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3697     char *bookHit = NULL; // [HGM] book
3698     Boolean weird = FALSE, reqFlag = FALSE;
3699
3700     fromX = fromY = toX = toY = -1;
3701     
3702     newGame = FALSE;
3703
3704     if (appData.debugMode)
3705       fprintf(debugFP, _("Parsing board: %s\n"), string);
3706
3707     move_str[0] = NULLCHAR;
3708     elapsed_time[0] = NULLCHAR;
3709     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3710         int  i = 0, j;
3711         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3712             if(string[i] == ' ') { ranks++; files = 0; }
3713             else files++;
3714             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3715             i++;
3716         }
3717         for(j = 0; j <i; j++) board_chars[j] = string[j];
3718         board_chars[i] = '\0';
3719         string += i + 1;
3720     }
3721     n = sscanf(string, PATTERN, &to_play, &double_push,
3722                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3723                &gamenum, white, black, &relation, &basetime, &increment,
3724                &white_stren, &black_stren, &white_time, &black_time,
3725                &moveNum, str, elapsed_time, move_str, &ics_flip,
3726                &ticking);
3727
3728     if (n < 21) {
3729         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3730         DisplayError(str, 0);
3731         return;
3732     }
3733
3734     /* Convert the move number to internal form */
3735     moveNum = (moveNum - 1) * 2;
3736     if (to_play == 'B') moveNum++;
3737     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3738       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3739                         0, 1);
3740       return;
3741     }
3742     
3743     switch (relation) {
3744       case RELATION_OBSERVING_PLAYED:
3745       case RELATION_OBSERVING_STATIC:
3746         if (gamenum == -1) {
3747             /* Old ICC buglet */
3748             relation = RELATION_OBSERVING_STATIC;
3749         }
3750         newGameMode = IcsObserving;
3751         break;
3752       case RELATION_PLAYING_MYMOVE:
3753       case RELATION_PLAYING_NOTMYMOVE:
3754         newGameMode =
3755           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3756             IcsPlayingWhite : IcsPlayingBlack;
3757         break;
3758       case RELATION_EXAMINING:
3759         newGameMode = IcsExamining;
3760         break;
3761       case RELATION_ISOLATED_BOARD:
3762       default:
3763         /* Just display this board.  If user was doing something else,
3764            we will forget about it until the next board comes. */ 
3765         newGameMode = IcsIdle;
3766         break;
3767       case RELATION_STARTING_POSITION:
3768         newGameMode = gameMode;
3769         break;
3770     }
3771     
3772     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3773          && newGameMode == IcsObserving && appData.bgObserve) {
3774       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3775       char buf[MSG_SIZ];
3776       for (k = 0; k < ranks; k++) {
3777         for (j = 0; j < files; j++)
3778           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3779         if(gameInfo.holdingsWidth > 1) {
3780              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3781              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3782         }
3783       }
3784       CopyBoard(partnerBoard, board);
3785       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3786       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3787                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3788       DisplayMessage(buf, "");
3789       return;
3790     }
3791
3792     /* Modify behavior for initial board display on move listing
3793        of wild games.
3794        */
3795     switch (ics_getting_history) {
3796       case H_FALSE:
3797       case H_REQUESTED:
3798         break;
3799       case H_GOT_REQ_HEADER:
3800       case H_GOT_UNREQ_HEADER:
3801         /* This is the initial position of the current game */
3802         gamenum = ics_gamenum;
3803         moveNum = 0;            /* old ICS bug workaround */
3804         if (to_play == 'B') {
3805           startedFromSetupPosition = TRUE;
3806           blackPlaysFirst = TRUE;
3807           moveNum = 1;
3808           if (forwardMostMove == 0) forwardMostMove = 1;
3809           if (backwardMostMove == 0) backwardMostMove = 1;
3810           if (currentMove == 0) currentMove = 1;
3811         }
3812         newGameMode = gameMode;
3813         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3814         break;
3815       case H_GOT_UNWANTED_HEADER:
3816         /* This is an initial board that we don't want */
3817         return;
3818       case H_GETTING_MOVES:
3819         /* Should not happen */
3820         DisplayError(_("Error gathering move list: extra board"), 0);
3821         ics_getting_history = H_FALSE;
3822         return;
3823     }
3824
3825    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3826                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3827      /* [HGM] We seem to have switched variant unexpectedly
3828       * Try to guess new variant from board size
3829       */
3830           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3831           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3832           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3833           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3834           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3835           if(!weird) newVariant = VariantNormal;
3836           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3837           /* Get a move list just to see the header, which
3838              will tell us whether this is really bug or zh */
3839           if (ics_getting_history == H_FALSE) {
3840             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3841             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3842             SendToICS(str);
3843           }
3844     }
3845     
3846     /* Take action if this is the first board of a new game, or of a
3847        different game than is currently being displayed.  */
3848     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3849         relation == RELATION_ISOLATED_BOARD) {
3850         
3851         /* Forget the old game and get the history (if any) of the new one */
3852         if (gameMode != BeginningOfGame) {
3853           Reset(TRUE, TRUE);
3854         }
3855         newGame = TRUE;
3856         if (appData.autoRaiseBoard) BoardToTop();
3857         prevMove = -3;
3858         if (gamenum == -1) {
3859             newGameMode = IcsIdle;
3860         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3861                    appData.getMoveList && !reqFlag) {
3862             /* Need to get game history */
3863             ics_getting_history = H_REQUESTED;
3864             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3865             SendToICS(str);
3866         }
3867         
3868         /* Initially flip the board to have black on the bottom if playing
3869            black or if the ICS flip flag is set, but let the user change
3870            it with the Flip View button. */
3871         flipView = appData.autoFlipView ? 
3872           (newGameMode == IcsPlayingBlack) || ics_flip :
3873           appData.flipView;
3874         
3875         /* Done with values from previous mode; copy in new ones */
3876         gameMode = newGameMode;
3877         ModeHighlight();
3878         ics_gamenum = gamenum;
3879         if (gamenum == gs_gamenum) {
3880             int klen = strlen(gs_kind);
3881             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3882             sprintf(str, "ICS %s", gs_kind);
3883             gameInfo.event = StrSave(str);
3884         } else {
3885             gameInfo.event = StrSave("ICS game");
3886         }
3887         gameInfo.site = StrSave(appData.icsHost);
3888         gameInfo.date = PGNDate();
3889         gameInfo.round = StrSave("-");
3890         gameInfo.white = StrSave(white);
3891         gameInfo.black = StrSave(black);
3892         timeControl = basetime * 60 * 1000;
3893         timeControl_2 = 0;
3894         timeIncrement = increment * 1000;
3895         movesPerSession = 0;
3896         gameInfo.timeControl = TimeControlTagValue();
3897         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3898   if (appData.debugMode) {
3899     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3900     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3901     setbuf(debugFP, NULL);
3902   }
3903
3904         gameInfo.outOfBook = NULL;
3905         
3906         /* Do we have the ratings? */
3907         if (strcmp(player1Name, white) == 0 &&
3908             strcmp(player2Name, black) == 0) {
3909             if (appData.debugMode)
3910               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3911                       player1Rating, player2Rating);
3912             gameInfo.whiteRating = player1Rating;
3913             gameInfo.blackRating = player2Rating;
3914         } else if (strcmp(player2Name, white) == 0 &&
3915                    strcmp(player1Name, black) == 0) {
3916             if (appData.debugMode)
3917               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3918                       player2Rating, player1Rating);
3919             gameInfo.whiteRating = player2Rating;
3920             gameInfo.blackRating = player1Rating;
3921         }
3922         player1Name[0] = player2Name[0] = NULLCHAR;
3923
3924         /* Silence shouts if requested */
3925         if (appData.quietPlay &&
3926             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3927             SendToICS(ics_prefix);
3928             SendToICS("set shout 0\n");
3929         }
3930     }
3931     
3932     /* Deal with midgame name changes */
3933     if (!newGame) {
3934         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3935             if (gameInfo.white) free(gameInfo.white);
3936             gameInfo.white = StrSave(white);
3937         }
3938         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3939             if (gameInfo.black) free(gameInfo.black);
3940             gameInfo.black = StrSave(black);
3941         }
3942     }
3943     
3944     /* Throw away game result if anything actually changes in examine mode */
3945     if (gameMode == IcsExamining && !newGame) {
3946         gameInfo.result = GameUnfinished;
3947         if (gameInfo.resultDetails != NULL) {
3948             free(gameInfo.resultDetails);
3949             gameInfo.resultDetails = NULL;
3950         }
3951     }
3952     
3953     /* In pausing && IcsExamining mode, we ignore boards coming
3954        in if they are in a different variation than we are. */
3955     if (pauseExamInvalid) return;
3956     if (pausing && gameMode == IcsExamining) {
3957         if (moveNum <= pauseExamForwardMostMove) {
3958             pauseExamInvalid = TRUE;
3959             forwardMostMove = pauseExamForwardMostMove;
3960             return;
3961         }
3962     }
3963     
3964   if (appData.debugMode) {
3965     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3966   }
3967     /* Parse the board */
3968     for (k = 0; k < ranks; k++) {
3969       for (j = 0; j < files; j++)
3970         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3971       if(gameInfo.holdingsWidth > 1) {
3972            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3973            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3974       }
3975     }
3976     CopyBoard(boards[moveNum], board);
3977     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3978     if (moveNum == 0) {
3979         startedFromSetupPosition =
3980           !CompareBoards(board, initialPosition);
3981         if(startedFromSetupPosition)
3982             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3983     }
3984
3985     /* [HGM] Set castling rights. Take the outermost Rooks,
3986        to make it also work for FRC opening positions. Note that board12
3987        is really defective for later FRC positions, as it has no way to
3988        indicate which Rook can castle if they are on the same side of King.
3989        For the initial position we grant rights to the outermost Rooks,
3990        and remember thos rights, and we then copy them on positions
3991        later in an FRC game. This means WB might not recognize castlings with
3992        Rooks that have moved back to their original position as illegal,
3993        but in ICS mode that is not its job anyway.
3994     */
3995     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3996     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3997
3998         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3999             if(board[0][i] == WhiteRook) j = i;
4000         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4001         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4002             if(board[0][i] == WhiteRook) j = i;
4003         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4004         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4005             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4006         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4007         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4008             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4009         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4010
4011         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4012         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4013             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4014         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4015             if(board[BOARD_HEIGHT-1][k] == bKing)
4016                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4017         if(gameInfo.variant == VariantTwoKings) {
4018             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4019             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4020             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4021         }
4022     } else { int r;
4023         r = boards[moveNum][CASTLING][0] = initialRights[0];
4024         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4025         r = boards[moveNum][CASTLING][1] = initialRights[1];
4026         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4027         r = boards[moveNum][CASTLING][3] = initialRights[3];
4028         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4029         r = boards[moveNum][CASTLING][4] = initialRights[4];
4030         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4031         /* wildcastle kludge: always assume King has rights */
4032         r = boards[moveNum][CASTLING][2] = initialRights[2];
4033         r = boards[moveNum][CASTLING][5] = initialRights[5];
4034     }
4035     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4036     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4037
4038     
4039     if (ics_getting_history == H_GOT_REQ_HEADER ||
4040         ics_getting_history == H_GOT_UNREQ_HEADER) {
4041         /* This was an initial position from a move list, not
4042            the current position */
4043         return;
4044     }
4045     
4046     /* Update currentMove and known move number limits */
4047     newMove = newGame || moveNum > forwardMostMove;
4048
4049     if (newGame) {
4050         forwardMostMove = backwardMostMove = currentMove = moveNum;
4051         if (gameMode == IcsExamining && moveNum == 0) {
4052           /* Workaround for ICS limitation: we are not told the wild
4053              type when starting to examine a game.  But if we ask for
4054              the move list, the move list header will tell us */
4055             ics_getting_history = H_REQUESTED;
4056             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4057             SendToICS(str);
4058         }
4059     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4060                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4061 #if ZIPPY
4062         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4063         /* [HGM] applied this also to an engine that is silently watching        */
4064         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4065             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4066             gameInfo.variant == currentlyInitializedVariant) {
4067           takeback = forwardMostMove - moveNum;
4068           for (i = 0; i < takeback; i++) {
4069             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4070             SendToProgram("undo\n", &first);
4071           }
4072         }
4073 #endif
4074
4075         forwardMostMove = moveNum;
4076         if (!pausing || currentMove > forwardMostMove)
4077           currentMove = forwardMostMove;
4078     } else {
4079         /* New part of history that is not contiguous with old part */ 
4080         if (pausing && gameMode == IcsExamining) {
4081             pauseExamInvalid = TRUE;
4082             forwardMostMove = pauseExamForwardMostMove;
4083             return;
4084         }
4085         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4086 #if ZIPPY
4087             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4088                 // [HGM] when we will receive the move list we now request, it will be
4089                 // fed to the engine from the first move on. So if the engine is not
4090                 // in the initial position now, bring it there.
4091                 InitChessProgram(&first, 0);
4092             }
4093 #endif
4094             ics_getting_history = H_REQUESTED;
4095             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4096             SendToICS(str);
4097         }
4098         forwardMostMove = backwardMostMove = currentMove = moveNum;
4099     }
4100     
4101     /* Update the clocks */
4102     if (strchr(elapsed_time, '.')) {
4103       /* Time is in ms */
4104       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4105       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4106     } else {
4107       /* Time is in seconds */
4108       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4109       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4110     }
4111       
4112
4113 #if ZIPPY
4114     if (appData.zippyPlay && newGame &&
4115         gameMode != IcsObserving && gameMode != IcsIdle &&
4116         gameMode != IcsExamining)
4117       ZippyFirstBoard(moveNum, basetime, increment);
4118 #endif
4119     
4120     /* Put the move on the move list, first converting
4121        to canonical algebraic form. */
4122     if (moveNum > 0) {
4123   if (appData.debugMode) {
4124     if (appData.debugMode) { int f = forwardMostMove;
4125         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4126                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4127                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4128     }
4129     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4130     fprintf(debugFP, "moveNum = %d\n", moveNum);
4131     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4132     setbuf(debugFP, NULL);
4133   }
4134         if (moveNum <= backwardMostMove) {
4135             /* We don't know what the board looked like before
4136                this move.  Punt. */
4137             strcpy(parseList[moveNum - 1], move_str);
4138             strcat(parseList[moveNum - 1], " ");
4139             strcat(parseList[moveNum - 1], elapsed_time);
4140             moveList[moveNum - 1][0] = NULLCHAR;
4141         } else if (strcmp(move_str, "none") == 0) {
4142             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4143             /* Again, we don't know what the board looked like;
4144                this is really the start of the game. */
4145             parseList[moveNum - 1][0] = NULLCHAR;
4146             moveList[moveNum - 1][0] = NULLCHAR;
4147             backwardMostMove = moveNum;
4148             startedFromSetupPosition = TRUE;
4149             fromX = fromY = toX = toY = -1;
4150         } else {
4151           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4152           //                 So we parse the long-algebraic move string in stead of the SAN move
4153           int valid; char buf[MSG_SIZ], *prom;
4154
4155           // str looks something like "Q/a1-a2"; kill the slash
4156           if(str[1] == '/') 
4157                 sprintf(buf, "%c%s", str[0], str+2);
4158           else  strcpy(buf, str); // might be castling
4159           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4160                 strcat(buf, prom); // long move lacks promo specification!
4161           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4162                 if(appData.debugMode) 
4163                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4164                 strcpy(move_str, buf);
4165           }
4166           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4167                                 &fromX, &fromY, &toX, &toY, &promoChar)
4168                || ParseOneMove(buf, moveNum - 1, &moveType,
4169                                 &fromX, &fromY, &toX, &toY, &promoChar);
4170           // end of long SAN patch
4171           if (valid) {
4172             (void) CoordsToAlgebraic(boards[moveNum - 1],
4173                                      PosFlags(moveNum - 1),
4174                                      fromY, fromX, toY, toX, promoChar,
4175                                      parseList[moveNum-1]);
4176             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4177               case MT_NONE:
4178               case MT_STALEMATE:
4179               default:
4180                 break;
4181               case MT_CHECK:
4182                 if(gameInfo.variant != VariantShogi)
4183                     strcat(parseList[moveNum - 1], "+");
4184                 break;
4185               case MT_CHECKMATE:
4186               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4187                 strcat(parseList[moveNum - 1], "#");
4188                 break;
4189             }
4190             strcat(parseList[moveNum - 1], " ");
4191             strcat(parseList[moveNum - 1], elapsed_time);
4192             /* currentMoveString is set as a side-effect of ParseOneMove */
4193             strcpy(moveList[moveNum - 1], currentMoveString);
4194             strcat(moveList[moveNum - 1], "\n");
4195           } else {
4196             /* Move from ICS was illegal!?  Punt. */
4197   if (appData.debugMode) {
4198     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4199     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4200   }
4201             strcpy(parseList[moveNum - 1], move_str);
4202             strcat(parseList[moveNum - 1], " ");
4203             strcat(parseList[moveNum - 1], elapsed_time);
4204             moveList[moveNum - 1][0] = NULLCHAR;
4205             fromX = fromY = toX = toY = -1;
4206           }
4207         }
4208   if (appData.debugMode) {
4209     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4210     setbuf(debugFP, NULL);
4211   }
4212
4213 #if ZIPPY
4214         /* Send move to chess program (BEFORE animating it). */
4215         if (appData.zippyPlay && !newGame && newMove && 
4216            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4217
4218             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4219                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4220                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4221                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4222                             move_str);
4223                     DisplayError(str, 0);
4224                 } else {
4225                     if (first.sendTime) {
4226                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4227                     }
4228                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4229                     if (firstMove && !bookHit) {
4230                         firstMove = FALSE;
4231                         if (first.useColors) {
4232                           SendToProgram(gameMode == IcsPlayingWhite ?
4233                                         "white\ngo\n" :
4234                                         "black\ngo\n", &first);
4235                         } else {
4236                           SendToProgram("go\n", &first);
4237                         }
4238                         first.maybeThinking = TRUE;
4239                     }
4240                 }
4241             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4242               if (moveList[moveNum - 1][0] == NULLCHAR) {
4243                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4244                 DisplayError(str, 0);
4245               } else {
4246                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4247                 SendMoveToProgram(moveNum - 1, &first);
4248               }
4249             }
4250         }
4251 #endif
4252     }
4253
4254     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4255         /* If move comes from a remote source, animate it.  If it
4256            isn't remote, it will have already been animated. */
4257         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4258             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4259         }
4260         if (!pausing && appData.highlightLastMove) {
4261             SetHighlights(fromX, fromY, toX, toY);
4262         }
4263     }
4264     
4265     /* Start the clocks */
4266     whiteFlag = blackFlag = FALSE;
4267     appData.clockMode = !(basetime == 0 && increment == 0);
4268     if (ticking == 0) {
4269       ics_clock_paused = TRUE;
4270       StopClocks();
4271     } else if (ticking == 1) {
4272       ics_clock_paused = FALSE;
4273     }
4274     if (gameMode == IcsIdle ||
4275         relation == RELATION_OBSERVING_STATIC ||
4276         relation == RELATION_EXAMINING ||
4277         ics_clock_paused)
4278       DisplayBothClocks();
4279     else
4280       StartClocks();
4281     
4282     /* Display opponents and material strengths */
4283     if (gameInfo.variant != VariantBughouse &&
4284         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4285         if (tinyLayout || smallLayout) {
4286             if(gameInfo.variant == VariantNormal)
4287                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4288                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4289                     basetime, increment);
4290             else
4291                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4292                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4293                     basetime, increment, (int) gameInfo.variant);
4294         } else {
4295             if(gameInfo.variant == VariantNormal)
4296                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4297                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4298                     basetime, increment);
4299             else
4300                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4301                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4302                     basetime, increment, VariantName(gameInfo.variant));
4303         }
4304         DisplayTitle(str);
4305   if (appData.debugMode) {
4306     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4307   }
4308     }
4309
4310
4311     /* Display the board */
4312     if (!pausing && !appData.noGUI) {
4313       
4314       if (appData.premove)
4315           if (!gotPremove || 
4316              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4317              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4318               ClearPremoveHighlights();
4319
4320       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4321       DrawPosition(j, boards[currentMove]);
4322
4323       DisplayMove(moveNum - 1);
4324       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4325             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4326               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4327         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4328       }
4329     }
4330
4331     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4332 #if ZIPPY
4333     if(bookHit) { // [HGM] book: simulate book reply
4334         static char bookMove[MSG_SIZ]; // a bit generous?
4335
4336         programStats.nodes = programStats.depth = programStats.time = 
4337         programStats.score = programStats.got_only_move = 0;
4338         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4339
4340         strcpy(bookMove, "move ");
4341         strcat(bookMove, bookHit);
4342         HandleMachineMove(bookMove, &first);
4343     }
4344 #endif
4345 }
4346
4347 void
4348 GetMoveListEvent()
4349 {
4350     char buf[MSG_SIZ];
4351     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4352         ics_getting_history = H_REQUESTED;
4353         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4354         SendToICS(buf);
4355     }
4356 }
4357
4358 void
4359 AnalysisPeriodicEvent(force)
4360      int force;
4361 {
4362     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4363          && !force) || !appData.periodicUpdates)
4364       return;
4365
4366     /* Send . command to Crafty to collect stats */
4367     SendToProgram(".\n", &first);
4368
4369     /* Don't send another until we get a response (this makes
4370        us stop sending to old Crafty's which don't understand
4371        the "." command (sending illegal cmds resets node count & time,
4372        which looks bad)) */
4373     programStats.ok_to_send = 0;
4374 }
4375
4376 void ics_update_width(new_width)
4377         int new_width;
4378 {
4379         ics_printf("set width %d\n", new_width);
4380 }
4381
4382 void
4383 SendMoveToProgram(moveNum, cps)
4384      int moveNum;
4385      ChessProgramState *cps;
4386 {
4387     char buf[MSG_SIZ];
4388
4389     if (cps->useUsermove) {
4390       SendToProgram("usermove ", cps);
4391     }
4392     if (cps->useSAN) {
4393       char *space;
4394       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4395         int len = space - parseList[moveNum];
4396         memcpy(buf, parseList[moveNum], len);
4397         buf[len++] = '\n';
4398         buf[len] = NULLCHAR;
4399       } else {
4400         sprintf(buf, "%s\n", parseList[moveNum]);
4401       }
4402       SendToProgram(buf, cps);
4403     } else {
4404       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4405         AlphaRank(moveList[moveNum], 4);
4406         SendToProgram(moveList[moveNum], cps);
4407         AlphaRank(moveList[moveNum], 4); // and back
4408       } else
4409       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4410        * the engine. It would be nice to have a better way to identify castle 
4411        * moves here. */
4412       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4413                                                                          && cps->useOOCastle) {
4414         int fromX = moveList[moveNum][0] - AAA; 
4415         int fromY = moveList[moveNum][1] - ONE;
4416         int toX = moveList[moveNum][2] - AAA; 
4417         int toY = moveList[moveNum][3] - ONE;
4418         if((boards[moveNum][fromY][fromX] == WhiteKing 
4419             && boards[moveNum][toY][toX] == WhiteRook)
4420            || (boards[moveNum][fromY][fromX] == BlackKing 
4421                && boards[moveNum][toY][toX] == BlackRook)) {
4422           if(toX > fromX) SendToProgram("O-O\n", cps);
4423           else SendToProgram("O-O-O\n", cps);
4424         }
4425         else SendToProgram(moveList[moveNum], cps);
4426       }
4427       else SendToProgram(moveList[moveNum], cps);
4428       /* End of additions by Tord */
4429     }
4430
4431     /* [HGM] setting up the opening has brought engine in force mode! */
4432     /*       Send 'go' if we are in a mode where machine should play. */
4433     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4434         (gameMode == TwoMachinesPlay   ||
4435 #ifdef ZIPPY
4436          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4437 #endif
4438          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4439         SendToProgram("go\n", cps);
4440   if (appData.debugMode) {
4441     fprintf(debugFP, "(extra)\n");
4442   }
4443     }
4444     setboardSpoiledMachineBlack = 0;
4445 }
4446
4447 void
4448 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4449      ChessMove moveType;
4450      int fromX, fromY, toX, toY;
4451 {
4452     char user_move[MSG_SIZ];
4453
4454     switch (moveType) {
4455       default:
4456         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4457                 (int)moveType, fromX, fromY, toX, toY);
4458         DisplayError(user_move + strlen("say "), 0);
4459         break;
4460       case WhiteKingSideCastle:
4461       case BlackKingSideCastle:
4462       case WhiteQueenSideCastleWild:
4463       case BlackQueenSideCastleWild:
4464       /* PUSH Fabien */
4465       case WhiteHSideCastleFR:
4466       case BlackHSideCastleFR:
4467       /* POP Fabien */
4468         sprintf(user_move, "o-o\n");
4469         break;
4470       case WhiteQueenSideCastle:
4471       case BlackQueenSideCastle:
4472       case WhiteKingSideCastleWild:
4473       case BlackKingSideCastleWild:
4474       /* PUSH Fabien */
4475       case WhiteASideCastleFR:
4476       case BlackASideCastleFR:
4477       /* POP Fabien */
4478         sprintf(user_move, "o-o-o\n");
4479         break;
4480       case WhitePromotionQueen:
4481       case BlackPromotionQueen:
4482       case WhitePromotionRook:
4483       case BlackPromotionRook:
4484       case WhitePromotionBishop:
4485       case BlackPromotionBishop:
4486       case WhitePromotionKnight:
4487       case BlackPromotionKnight:
4488       case WhitePromotionKing:
4489       case BlackPromotionKing:
4490       case WhitePromotionChancellor:
4491       case BlackPromotionChancellor:
4492       case WhitePromotionArchbishop:
4493       case BlackPromotionArchbishop:
4494         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4495             sprintf(user_move, "%c%c%c%c=%c\n",
4496                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4497                 PieceToChar(WhiteFerz));
4498         else if(gameInfo.variant == VariantGreat)
4499             sprintf(user_move, "%c%c%c%c=%c\n",
4500                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4501                 PieceToChar(WhiteMan));
4502         else
4503             sprintf(user_move, "%c%c%c%c=%c\n",
4504                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4505                 PieceToChar(PromoPiece(moveType)));
4506         break;
4507       case WhiteDrop:
4508       case BlackDrop:
4509         sprintf(user_move, "%c@%c%c\n",
4510                 ToUpper(PieceToChar((ChessSquare) fromX)),
4511                 AAA + toX, ONE + toY);
4512         break;
4513       case NormalMove:
4514       case WhiteCapturesEnPassant:
4515       case BlackCapturesEnPassant:
4516       case IllegalMove:  /* could be a variant we don't quite understand */
4517         sprintf(user_move, "%c%c%c%c\n",
4518                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4519         break;
4520     }
4521     SendToICS(user_move);
4522     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4523         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4524 }
4525
4526 void
4527 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4528      int rf, ff, rt, ft;
4529      char promoChar;
4530      char move[7];
4531 {
4532     if (rf == DROP_RANK) {
4533         sprintf(move, "%c@%c%c\n",
4534                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4535     } else {
4536         if (promoChar == 'x' || promoChar == NULLCHAR) {
4537             sprintf(move, "%c%c%c%c\n",
4538                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4539         } else {
4540             sprintf(move, "%c%c%c%c%c\n",
4541                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4542         }
4543     }
4544 }
4545
4546 void
4547 ProcessICSInitScript(f)
4548      FILE *f;
4549 {
4550     char buf[MSG_SIZ];
4551
4552     while (fgets(buf, MSG_SIZ, f)) {
4553         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4554     }
4555
4556     fclose(f);
4557 }
4558
4559
4560 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4561 void
4562 AlphaRank(char *move, int n)
4563 {
4564 //    char *p = move, c; int x, y;
4565
4566     if (appData.debugMode) {
4567         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4568     }
4569
4570     if(move[1]=='*' && 
4571        move[2]>='0' && move[2]<='9' &&
4572        move[3]>='a' && move[3]<='x'    ) {
4573         move[1] = '@';
4574         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4575         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4576     } else
4577     if(move[0]>='0' && move[0]<='9' &&
4578        move[1]>='a' && move[1]<='x' &&
4579        move[2]>='0' && move[2]<='9' &&
4580        move[3]>='a' && move[3]<='x'    ) {
4581         /* input move, Shogi -> normal */
4582         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4583         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4584         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4585         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4586     } else
4587     if(move[1]=='@' &&
4588        move[3]>='0' && move[3]<='9' &&
4589        move[2]>='a' && move[2]<='x'    ) {
4590         move[1] = '*';
4591         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4592         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4593     } else
4594     if(
4595        move[0]>='a' && move[0]<='x' &&
4596        move[3]>='0' && move[3]<='9' &&
4597        move[2]>='a' && move[2]<='x'    ) {
4598          /* output move, normal -> Shogi */
4599         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4600         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4601         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4602         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4603         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4604     }
4605     if (appData.debugMode) {
4606         fprintf(debugFP, "   out = '%s'\n", move);
4607     }
4608 }
4609
4610 /* Parser for moves from gnuchess, ICS, or user typein box */
4611 Boolean
4612 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4613      char *move;
4614      int moveNum;
4615      ChessMove *moveType;
4616      int *fromX, *fromY, *toX, *toY;
4617      char *promoChar;
4618 {       
4619     if (appData.debugMode) {
4620         fprintf(debugFP, "move to parse: %s\n", move);
4621     }
4622     *moveType = yylexstr(moveNum, move);
4623
4624     switch (*moveType) {
4625       case WhitePromotionChancellor:
4626       case BlackPromotionChancellor:
4627       case WhitePromotionArchbishop:
4628       case BlackPromotionArchbishop:
4629       case WhitePromotionQueen:
4630       case BlackPromotionQueen:
4631       case WhitePromotionRook:
4632       case BlackPromotionRook:
4633       case WhitePromotionBishop:
4634       case BlackPromotionBishop:
4635       case WhitePromotionKnight:
4636       case BlackPromotionKnight:
4637       case WhitePromotionKing:
4638       case BlackPromotionKing:
4639       case NormalMove:
4640       case WhiteCapturesEnPassant:
4641       case BlackCapturesEnPassant:
4642       case WhiteKingSideCastle:
4643       case WhiteQueenSideCastle:
4644       case BlackKingSideCastle:
4645       case BlackQueenSideCastle:
4646       case WhiteKingSideCastleWild:
4647       case WhiteQueenSideCastleWild:
4648       case BlackKingSideCastleWild:
4649       case BlackQueenSideCastleWild:
4650       /* Code added by Tord: */
4651       case WhiteHSideCastleFR:
4652       case WhiteASideCastleFR:
4653       case BlackHSideCastleFR:
4654       case BlackASideCastleFR:
4655       /* End of code added by Tord */
4656       case IllegalMove:         /* bug or odd chess variant */
4657         *fromX = currentMoveString[0] - AAA;
4658         *fromY = currentMoveString[1] - ONE;
4659         *toX = currentMoveString[2] - AAA;
4660         *toY = currentMoveString[3] - ONE;
4661         *promoChar = currentMoveString[4];
4662         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4663             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4664     if (appData.debugMode) {
4665         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4666     }
4667             *fromX = *fromY = *toX = *toY = 0;
4668             return FALSE;
4669         }
4670         if (appData.testLegality) {
4671           return (*moveType != IllegalMove);
4672         } else {
4673           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4674                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4675         }
4676
4677       case WhiteDrop:
4678       case BlackDrop:
4679         *fromX = *moveType == WhiteDrop ?
4680           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4681           (int) CharToPiece(ToLower(currentMoveString[0]));
4682         *fromY = DROP_RANK;
4683         *toX = currentMoveString[2] - AAA;
4684         *toY = currentMoveString[3] - ONE;
4685         *promoChar = NULLCHAR;
4686         return TRUE;
4687
4688       case AmbiguousMove:
4689       case ImpossibleMove:
4690       case (ChessMove) 0:       /* end of file */
4691       case ElapsedTime:
4692       case Comment:
4693       case PGNTag:
4694       case NAG:
4695       case WhiteWins:
4696       case BlackWins:
4697       case GameIsDrawn:
4698       default:
4699     if (appData.debugMode) {
4700         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4701     }
4702         /* bug? */
4703         *fromX = *fromY = *toX = *toY = 0;
4704         *promoChar = NULLCHAR;
4705         return FALSE;
4706     }
4707 }
4708
4709
4710 void
4711 ParsePV(char *pv)
4712 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4713   int fromX, fromY, toX, toY; char promoChar;
4714   ChessMove moveType;
4715   Boolean valid;
4716   int nr = 0;
4717
4718   endPV = forwardMostMove;
4719   do {
4720     while(*pv == ' ') pv++;
4721     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4722     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4723 if(appData.debugMode){
4724 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4725 }
4726     if(!valid && nr == 0 &&
4727        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4728         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4729     }
4730     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4731     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4732     nr++;
4733     if(endPV+1 > framePtr) break; // no space, truncate
4734     if(!valid) break;
4735     endPV++;
4736     CopyBoard(boards[endPV], boards[endPV-1]);
4737     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4738     moveList[endPV-1][0] = fromX + AAA;
4739     moveList[endPV-1][1] = fromY + ONE;
4740     moveList[endPV-1][2] = toX + AAA;
4741     moveList[endPV-1][3] = toY + ONE;
4742     parseList[endPV-1][0] = NULLCHAR;
4743   } while(valid);
4744   currentMove = endPV;
4745   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4746   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4747                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4748   DrawPosition(TRUE, boards[currentMove]);
4749 }
4750
4751 static int lastX, lastY;
4752
4753 Boolean
4754 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4755 {
4756         int startPV;
4757
4758         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4759         lastX = x; lastY = y;
4760         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4761         startPV = index;
4762       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4763       index = startPV;
4764         while(buf[index] && buf[index] != '\n') index++;
4765         buf[index] = 0;
4766         ParsePV(buf+startPV);
4767         *start = startPV; *end = index-1;
4768         return TRUE;
4769 }
4770
4771 Boolean
4772 LoadPV(int x, int y)
4773 { // called on right mouse click to load PV
4774   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4775   lastX = x; lastY = y;
4776   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4777   return TRUE;
4778 }
4779
4780 void
4781 UnLoadPV()
4782 {
4783   if(endPV < 0) return;
4784   endPV = -1;
4785   currentMove = forwardMostMove;
4786   ClearPremoveHighlights();
4787   DrawPosition(TRUE, boards[currentMove]);
4788 }
4789
4790 void
4791 MovePV(int x, int y, int h)
4792 { // step through PV based on mouse coordinates (called on mouse move)
4793   int margin = h>>3, step = 0;
4794
4795   if(endPV < 0) return;
4796   // we must somehow check if right button is still down (might be released off board!)
4797   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4798   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4799   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4800   if(!step) return;
4801   lastX = x; lastY = y;
4802   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4803   currentMove += step;
4804   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4805   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4806                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4807   DrawPosition(FALSE, boards[currentMove]);
4808 }
4809
4810
4811 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4812 // All positions will have equal probability, but the current method will not provide a unique
4813 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4814 #define DARK 1
4815 #define LITE 2
4816 #define ANY 3
4817
4818 int squaresLeft[4];
4819 int piecesLeft[(int)BlackPawn];
4820 int seed, nrOfShuffles;
4821
4822 void GetPositionNumber()
4823 {       // sets global variable seed
4824         int i;
4825
4826         seed = appData.defaultFrcPosition;
4827         if(seed < 0) { // randomize based on time for negative FRC position numbers
4828                 for(i=0; i<50; i++) seed += random();
4829                 seed = random() ^ random() >> 8 ^ random() << 8;
4830                 if(seed<0) seed = -seed;
4831         }
4832 }
4833
4834 int put(Board board, int pieceType, int rank, int n, int shade)
4835 // put the piece on the (n-1)-th empty squares of the given shade
4836 {
4837         int i;
4838
4839         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4840                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4841                         board[rank][i] = (ChessSquare) pieceType;
4842                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4843                         squaresLeft[ANY]--;
4844                         piecesLeft[pieceType]--; 
4845                         return i;
4846                 }
4847         }
4848         return -1;
4849 }
4850
4851
4852 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4853 // calculate where the next piece goes, (any empty square), and put it there
4854 {
4855         int i;
4856
4857         i = seed % squaresLeft[shade];
4858         nrOfShuffles *= squaresLeft[shade];
4859         seed /= squaresLeft[shade];
4860         put(board, pieceType, rank, i, shade);
4861 }
4862
4863 void AddTwoPieces(Board board, int pieceType, int rank)
4864 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4865 {
4866         int i, n=squaresLeft[ANY], j=n-1, k;
4867
4868         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4869         i = seed % k;  // pick one
4870         nrOfShuffles *= k;
4871         seed /= k;
4872         while(i >= j) i -= j--;
4873         j = n - 1 - j; i += j;
4874         put(board, pieceType, rank, j, ANY);
4875         put(board, pieceType, rank, i, ANY);
4876 }
4877
4878 void SetUpShuffle(Board board, int number)
4879 {
4880         int i, p, first=1;
4881
4882         GetPositionNumber(); nrOfShuffles = 1;
4883
4884         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4885         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4886         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4887
4888         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4889
4890         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4891             p = (int) board[0][i];
4892             if(p < (int) BlackPawn) piecesLeft[p] ++;
4893             board[0][i] = EmptySquare;
4894         }
4895
4896         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4897             // shuffles restricted to allow normal castling put KRR first
4898             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4899                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4900             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4901                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4902             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4903                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4904             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4905                 put(board, WhiteRook, 0, 0, ANY);
4906             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4907         }
4908
4909         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4910             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4911             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4912                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4913                 while(piecesLeft[p] >= 2) {
4914                     AddOnePiece(board, p, 0, LITE);
4915                     AddOnePiece(board, p, 0, DARK);
4916                 }
4917                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4918             }
4919
4920         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4921             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4922             // but we leave King and Rooks for last, to possibly obey FRC restriction
4923             if(p == (int)WhiteRook) continue;
4924             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4925             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4926         }
4927
4928         // now everything is placed, except perhaps King (Unicorn) and Rooks
4929
4930         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4931             // Last King gets castling rights
4932             while(piecesLeft[(int)WhiteUnicorn]) {
4933                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4934                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4935             }
4936
4937             while(piecesLeft[(int)WhiteKing]) {
4938                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4939                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4940             }
4941
4942
4943         } else {
4944             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4945             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4946         }
4947
4948         // Only Rooks can be left; simply place them all
4949         while(piecesLeft[(int)WhiteRook]) {
4950                 i = put(board, WhiteRook, 0, 0, ANY);
4951                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4952                         if(first) {
4953                                 first=0;
4954                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4955                         }
4956                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4957                 }
4958         }
4959         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4960             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4961         }
4962
4963         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4964 }
4965
4966 int SetCharTable( char *table, const char * map )
4967 /* [HGM] moved here from winboard.c because of its general usefulness */
4968 /*       Basically a safe strcpy that uses the last character as King */
4969 {
4970     int result = FALSE; int NrPieces;
4971
4972     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4973                     && NrPieces >= 12 && !(NrPieces&1)) {
4974         int i; /* [HGM] Accept even length from 12 to 34 */
4975
4976         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4977         for( i=0; i<NrPieces/2-1; i++ ) {
4978             table[i] = map[i];
4979             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4980         }
4981         table[(int) WhiteKing]  = map[NrPieces/2-1];
4982         table[(int) BlackKing]  = map[NrPieces-1];
4983
4984         result = TRUE;
4985     }
4986
4987     return result;
4988 }
4989
4990 void Prelude(Board board)
4991 {       // [HGM] superchess: random selection of exo-pieces
4992         int i, j, k; ChessSquare p; 
4993         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4994
4995         GetPositionNumber(); // use FRC position number
4996
4997         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4998             SetCharTable(pieceToChar, appData.pieceToCharTable);
4999             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5000                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5001         }
5002
5003         j = seed%4;                 seed /= 4; 
5004         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5005         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5006         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5007         j = seed%3 + (seed%3 >= j); seed /= 3; 
5008         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5009         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5010         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5011         j = seed%3;                 seed /= 3; 
5012         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5013         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5014         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5015         j = seed%2 + (seed%2 >= j); seed /= 2; 
5016         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5017         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5018         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5019         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5020         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5021         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5022         put(board, exoPieces[0],    0, 0, ANY);
5023         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5024 }
5025
5026 void
5027 InitPosition(redraw)
5028      int redraw;
5029 {
5030     ChessSquare (* pieces)[BOARD_FILES];
5031     int i, j, pawnRow, overrule,
5032     oldx = gameInfo.boardWidth,
5033     oldy = gameInfo.boardHeight,
5034     oldh = gameInfo.holdingsWidth,
5035     oldv = gameInfo.variant;
5036
5037     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5038
5039     /* [AS] Initialize pv info list [HGM] and game status */
5040     {
5041         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5042             pvInfoList[i].depth = 0;
5043             boards[i][EP_STATUS] = EP_NONE;
5044             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5045         }
5046
5047         initialRulePlies = 0; /* 50-move counter start */
5048
5049         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5050         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5051     }
5052
5053     
5054     /* [HGM] logic here is completely changed. In stead of full positions */
5055     /* the initialized data only consist of the two backranks. The switch */
5056     /* selects which one we will use, which is than copied to the Board   */
5057     /* initialPosition, which for the rest is initialized by Pawns and    */
5058     /* empty squares. This initial position is then copied to boards[0],  */
5059     /* possibly after shuffling, so that it remains available.            */
5060
5061     gameInfo.holdingsWidth = 0; /* default board sizes */
5062     gameInfo.boardWidth    = 8;
5063     gameInfo.boardHeight   = 8;
5064     gameInfo.holdingsSize  = 0;
5065     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5066     for(i=0; i<BOARD_FILES-2; i++)
5067       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5068     initialPosition[EP_STATUS] = EP_NONE;
5069     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5070
5071     switch (gameInfo.variant) {
5072     case VariantFischeRandom:
5073       shuffleOpenings = TRUE;
5074     default:
5075       pieces = FIDEArray;
5076       break;
5077     case VariantShatranj:
5078       pieces = ShatranjArray;
5079       nrCastlingRights = 0;
5080       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5081       break;
5082     case VariantMakruk:
5083       pieces = makrukArray;
5084       nrCastlingRights = 0;
5085       startedFromSetupPosition = TRUE;
5086       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5087       break;
5088     case VariantTwoKings:
5089       pieces = twoKingsArray;
5090       break;
5091     case VariantCapaRandom:
5092       shuffleOpenings = TRUE;
5093     case VariantCapablanca:
5094       pieces = CapablancaArray;
5095       gameInfo.boardWidth = 10;
5096       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5097       break;
5098     case VariantGothic:
5099       pieces = GothicArray;
5100       gameInfo.boardWidth = 10;
5101       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5102       break;
5103     case VariantJanus:
5104       pieces = JanusArray;
5105       gameInfo.boardWidth = 10;
5106       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5107       nrCastlingRights = 6;
5108         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5109         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5110         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5111         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5112         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5113         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5114       break;
5115     case VariantFalcon:
5116       pieces = FalconArray;
5117       gameInfo.boardWidth = 10;
5118       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5119       break;
5120     case VariantXiangqi:
5121       pieces = XiangqiArray;
5122       gameInfo.boardWidth  = 9;
5123       gameInfo.boardHeight = 10;
5124       nrCastlingRights = 0;
5125       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5126       break;
5127     case VariantShogi:
5128       pieces = ShogiArray;
5129       gameInfo.boardWidth  = 9;
5130       gameInfo.boardHeight = 9;
5131       gameInfo.holdingsSize = 7;
5132       nrCastlingRights = 0;
5133       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5134       break;
5135     case VariantCourier:
5136       pieces = CourierArray;
5137       gameInfo.boardWidth  = 12;
5138       nrCastlingRights = 0;
5139       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5140       break;
5141     case VariantKnightmate:
5142       pieces = KnightmateArray;
5143       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5144       break;
5145     case VariantFairy:
5146       pieces = fairyArray;
5147       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5148       break;
5149     case VariantGreat:
5150       pieces = GreatArray;
5151       gameInfo.boardWidth = 10;
5152       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5153       gameInfo.holdingsSize = 8;
5154       break;
5155     case VariantSuper:
5156       pieces = FIDEArray;
5157       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5158       gameInfo.holdingsSize = 8;
5159       startedFromSetupPosition = TRUE;
5160       break;
5161     case VariantCrazyhouse:
5162     case VariantBughouse:
5163       pieces = FIDEArray;
5164       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5165       gameInfo.holdingsSize = 5;
5166       break;
5167     case VariantWildCastle:
5168       pieces = FIDEArray;
5169       /* !!?shuffle with kings guaranteed to be on d or e file */
5170       shuffleOpenings = 1;
5171       break;
5172     case VariantNoCastle:
5173       pieces = FIDEArray;
5174       nrCastlingRights = 0;
5175       /* !!?unconstrained back-rank shuffle */
5176       shuffleOpenings = 1;
5177       break;
5178     }
5179
5180     overrule = 0;
5181     if(appData.NrFiles >= 0) {
5182         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5183         gameInfo.boardWidth = appData.NrFiles;
5184     }
5185     if(appData.NrRanks >= 0) {
5186         gameInfo.boardHeight = appData.NrRanks;
5187     }
5188     if(appData.holdingsSize >= 0) {
5189         i = appData.holdingsSize;
5190         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5191         gameInfo.holdingsSize = i;
5192     }
5193     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5194     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5195         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5196
5197     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5198     if(pawnRow < 1) pawnRow = 1;
5199     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5200
5201     /* User pieceToChar list overrules defaults */
5202     if(appData.pieceToCharTable != NULL)
5203         SetCharTable(pieceToChar, appData.pieceToCharTable);
5204
5205     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5206
5207         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5208             s = (ChessSquare) 0; /* account holding counts in guard band */
5209         for( i=0; i<BOARD_HEIGHT; i++ )
5210             initialPosition[i][j] = s;
5211
5212         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5213         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5214         initialPosition[pawnRow][j] = WhitePawn;
5215         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5216         if(gameInfo.variant == VariantXiangqi) {
5217             if(j&1) {
5218                 initialPosition[pawnRow][j] = 
5219                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5220                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5221                    initialPosition[2][j] = WhiteCannon;
5222                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5223                 }
5224             }
5225         }
5226         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5227     }
5228     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5229
5230             j=BOARD_LEFT+1;
5231             initialPosition[1][j] = WhiteBishop;
5232             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5233             j=BOARD_RGHT-2;
5234             initialPosition[1][j] = WhiteRook;
5235             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5236     }
5237
5238     if( nrCastlingRights == -1) {
5239         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5240         /*       This sets default castling rights from none to normal corners   */
5241         /* Variants with other castling rights must set them themselves above    */
5242         nrCastlingRights = 6;
5243        
5244         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5245         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5246         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5247         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5248         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5249         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5250      }
5251
5252      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5253      if(gameInfo.variant == VariantGreat) { // promotion commoners
5254         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5255         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5256         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5257         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5258      }
5259   if (appData.debugMode) {
5260     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5261   }
5262     if(shuffleOpenings) {
5263         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5264         startedFromSetupPosition = TRUE;
5265     }
5266     if(startedFromPositionFile) {
5267       /* [HGM] loadPos: use PositionFile for every new game */
5268       CopyBoard(initialPosition, filePosition);
5269       for(i=0; i<nrCastlingRights; i++)
5270           initialRights[i] = filePosition[CASTLING][i];
5271       startedFromSetupPosition = TRUE;
5272     }
5273
5274     CopyBoard(boards[0], initialPosition);
5275
5276     if(oldx != gameInfo.boardWidth ||
5277        oldy != gameInfo.boardHeight ||
5278        oldh != gameInfo.holdingsWidth
5279 #ifdef GOTHIC
5280        || oldv == VariantGothic ||        // For licensing popups
5281        gameInfo.variant == VariantGothic
5282 #endif
5283 #ifdef FALCON
5284        || oldv == VariantFalcon ||
5285        gameInfo.variant == VariantFalcon
5286 #endif
5287                                          )
5288             InitDrawingSizes(-2 ,0);
5289
5290     if (redraw)
5291       DrawPosition(TRUE, boards[currentMove]);
5292 }
5293
5294 void
5295 SendBoard(cps, moveNum)
5296      ChessProgramState *cps;
5297      int moveNum;
5298 {
5299     char message[MSG_SIZ];
5300     
5301     if (cps->useSetboard) {
5302       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5303       sprintf(message, "setboard %s\n", fen);
5304       SendToProgram(message, cps);
5305       free(fen);
5306
5307     } else {
5308       ChessSquare *bp;
5309       int i, j;
5310       /* Kludge to set black to move, avoiding the troublesome and now
5311        * deprecated "black" command.
5312        */
5313       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5314
5315       SendToProgram("edit\n", cps);
5316       SendToProgram("#\n", cps);
5317       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5318         bp = &boards[moveNum][i][BOARD_LEFT];
5319         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5320           if ((int) *bp < (int) BlackPawn) {
5321             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5322                     AAA + j, ONE + i);
5323             if(message[0] == '+' || message[0] == '~') {
5324                 sprintf(message, "%c%c%c+\n",
5325                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5326                         AAA + j, ONE + i);
5327             }
5328             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5329                 message[1] = BOARD_RGHT   - 1 - j + '1';
5330                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5331             }
5332             SendToProgram(message, cps);
5333           }
5334         }
5335       }
5336     
5337       SendToProgram("c\n", cps);
5338       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5339         bp = &boards[moveNum][i][BOARD_LEFT];
5340         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5341           if (((int) *bp != (int) EmptySquare)
5342               && ((int) *bp >= (int) BlackPawn)) {
5343             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5344                     AAA + j, ONE + i);
5345             if(message[0] == '+' || message[0] == '~') {
5346                 sprintf(message, "%c%c%c+\n",
5347                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5348                         AAA + j, ONE + i);
5349             }
5350             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5351                 message[1] = BOARD_RGHT   - 1 - j + '1';
5352                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5353             }
5354             SendToProgram(message, cps);
5355           }
5356         }
5357       }
5358     
5359       SendToProgram(".\n", cps);
5360     }
5361     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5362 }
5363
5364 static int autoQueen; // [HGM] oneclick
5365
5366 int
5367 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5368 {
5369     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5370     /* [HGM] add Shogi promotions */
5371     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5372     ChessSquare piece;
5373     ChessMove moveType;
5374     Boolean premove;
5375
5376     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5377     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5378
5379     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5380       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5381         return FALSE;
5382
5383     piece = boards[currentMove][fromY][fromX];
5384     if(gameInfo.variant == VariantShogi) {
5385         promotionZoneSize = 3;
5386         highestPromotingPiece = (int)WhiteFerz;
5387     } else if(gameInfo.variant == VariantMakruk) {
5388         promotionZoneSize = 3;
5389     }
5390
5391     // next weed out all moves that do not touch the promotion zone at all
5392     if((int)piece >= BlackPawn) {
5393         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5394              return FALSE;
5395         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5396     } else {
5397         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5398            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5399     }
5400
5401     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5402
5403     // weed out mandatory Shogi promotions
5404     if(gameInfo.variant == VariantShogi) {
5405         if(piece >= BlackPawn) {
5406             if(toY == 0 && piece == BlackPawn ||
5407                toY == 0 && piece == BlackQueen ||
5408                toY <= 1 && piece == BlackKnight) {
5409                 *promoChoice = '+';
5410                 return FALSE;
5411             }
5412         } else {
5413             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5414                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5415                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5416                 *promoChoice = '+';
5417                 return FALSE;
5418             }
5419         }
5420     }
5421
5422     // weed out obviously illegal Pawn moves
5423     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5424         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5425         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5426         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5427         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5428         // note we are not allowed to test for valid (non-)capture, due to premove
5429     }
5430
5431     // we either have a choice what to promote to, or (in Shogi) whether to promote
5432     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5433         *promoChoice = PieceToChar(BlackFerz);  // no choice
5434         return FALSE;
5435     }
5436     if(autoQueen) { // predetermined
5437         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5438              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5439         else *promoChoice = PieceToChar(BlackQueen);
5440         return FALSE;
5441     }
5442
5443     // suppress promotion popup on illegal moves that are not premoves
5444     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5445               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5446     if(appData.testLegality && !premove) {
5447         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5448                         fromY, fromX, toY, toX, NULLCHAR);
5449         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5450            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5451             return FALSE;
5452     }
5453
5454     return TRUE;
5455 }
5456
5457 int
5458 InPalace(row, column)
5459      int row, column;
5460 {   /* [HGM] for Xiangqi */
5461     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5462          column < (BOARD_WIDTH + 4)/2 &&
5463          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5464     return FALSE;
5465 }
5466
5467 int
5468 PieceForSquare (x, y)
5469      int x;
5470      int y;
5471 {
5472   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5473      return -1;
5474   else
5475      return boards[currentMove][y][x];
5476 }
5477
5478 int
5479 OKToStartUserMove(x, y)
5480      int x, y;
5481 {
5482     ChessSquare from_piece;
5483     int white_piece;
5484
5485     if (matchMode) return FALSE;
5486     if (gameMode == EditPosition) return TRUE;
5487
5488     if (x >= 0 && y >= 0)
5489       from_piece = boards[currentMove][y][x];
5490     else
5491       from_piece = EmptySquare;
5492
5493     if (from_piece == EmptySquare) return FALSE;
5494
5495     white_piece = (int)from_piece >= (int)WhitePawn &&
5496       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5497
5498     switch (gameMode) {
5499       case PlayFromGameFile:
5500       case AnalyzeFile:
5501       case TwoMachinesPlay:
5502       case EndOfGame:
5503         return FALSE;
5504
5505       case IcsObserving:
5506       case IcsIdle:
5507         return FALSE;
5508
5509       case MachinePlaysWhite:
5510       case IcsPlayingBlack:
5511         if (appData.zippyPlay) return FALSE;
5512         if (white_piece) {
5513             DisplayMoveError(_("You are playing Black"));
5514             return FALSE;
5515         }
5516         break;
5517
5518       case MachinePlaysBlack:
5519       case IcsPlayingWhite:
5520         if (appData.zippyPlay) return FALSE;
5521         if (!white_piece) {
5522             DisplayMoveError(_("You are playing White"));
5523             return FALSE;
5524         }
5525         break;
5526
5527       case EditGame:
5528         if (!white_piece && WhiteOnMove(currentMove)) {
5529             DisplayMoveError(_("It is White's turn"));
5530             return FALSE;
5531         }           
5532         if (white_piece && !WhiteOnMove(currentMove)) {
5533             DisplayMoveError(_("It is Black's turn"));
5534             return FALSE;
5535         }           
5536         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5537             /* Editing correspondence game history */
5538             /* Could disallow this or prompt for confirmation */
5539             cmailOldMove = -1;
5540         }
5541         break;
5542
5543       case BeginningOfGame:
5544         if (appData.icsActive) return FALSE;
5545         if (!appData.noChessProgram) {
5546             if (!white_piece) {
5547                 DisplayMoveError(_("You are playing White"));
5548                 return FALSE;
5549             }
5550         }
5551         break;
5552         
5553       case Training:
5554         if (!white_piece && WhiteOnMove(currentMove)) {
5555             DisplayMoveError(_("It is White's turn"));
5556             return FALSE;
5557         }           
5558         if (white_piece && !WhiteOnMove(currentMove)) {
5559             DisplayMoveError(_("It is Black's turn"));
5560             return FALSE;
5561         }           
5562         break;
5563
5564       default:
5565       case IcsExamining:
5566         break;
5567     }
5568     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5569         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5570         && gameMode != AnalyzeFile && gameMode != Training) {
5571         DisplayMoveError(_("Displayed position is not current"));
5572         return FALSE;
5573     }
5574     return TRUE;
5575 }
5576
5577 Boolean
5578 OnlyMove(int *x, int *y, Boolean captures) {
5579     DisambiguateClosure cl;
5580     if (appData.zippyPlay) return FALSE;
5581     switch(gameMode) {
5582       case MachinePlaysBlack:
5583       case IcsPlayingWhite:
5584       case BeginningOfGame:
5585         if(!WhiteOnMove(currentMove)) return FALSE;
5586         break;
5587       case MachinePlaysWhite:
5588       case IcsPlayingBlack:
5589         if(WhiteOnMove(currentMove)) return FALSE;
5590         break;
5591       default:
5592         return FALSE;
5593     }
5594     cl.pieceIn = EmptySquare; 
5595     cl.rfIn = *y;
5596     cl.ffIn = *x;
5597     cl.rtIn = -1;
5598     cl.ftIn = -1;
5599     cl.promoCharIn = NULLCHAR;
5600     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5601     if( cl.kind == NormalMove ||
5602         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5603         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5604         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5605         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5606       fromX = cl.ff;
5607       fromY = cl.rf;
5608       *x = cl.ft;
5609       *y = cl.rt;
5610       return TRUE;
5611     }
5612     if(cl.kind != ImpossibleMove) return FALSE;
5613     cl.pieceIn = EmptySquare;
5614     cl.rfIn = -1;
5615     cl.ffIn = -1;
5616     cl.rtIn = *y;
5617     cl.ftIn = *x;
5618     cl.promoCharIn = NULLCHAR;
5619     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5620     if( cl.kind == NormalMove ||
5621         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5622         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5623         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5624         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5625       fromX = cl.ff;
5626       fromY = cl.rf;
5627       *x = cl.ft;
5628       *y = cl.rt;
5629       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5630       return TRUE;
5631     }
5632     return FALSE;
5633 }
5634
5635 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5636 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5637 int lastLoadGameUseList = FALSE;
5638 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5639 ChessMove lastLoadGameStart = (ChessMove) 0;
5640
5641 ChessMove
5642 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5643      int fromX, fromY, toX, toY;
5644      int promoChar;
5645      Boolean captureOwn;
5646 {
5647     ChessMove moveType;
5648     ChessSquare pdown, pup;
5649
5650     /* Check if the user is playing in turn.  This is complicated because we
5651        let the user "pick up" a piece before it is his turn.  So the piece he
5652        tried to pick up may have been captured by the time he puts it down!
5653        Therefore we use the color the user is supposed to be playing in this
5654        test, not the color of the piece that is currently on the starting
5655        square---except in EditGame mode, where the user is playing both
5656        sides; fortunately there the capture race can't happen.  (It can
5657        now happen in IcsExamining mode, but that's just too bad.  The user
5658        will get a somewhat confusing message in that case.)
5659        */
5660
5661     switch (gameMode) {
5662       case PlayFromGameFile:
5663       case AnalyzeFile:
5664       case TwoMachinesPlay:
5665       case EndOfGame:
5666       case IcsObserving:
5667       case IcsIdle:
5668         /* We switched into a game mode where moves are not accepted,
5669            perhaps while the mouse button was down. */
5670         return ImpossibleMove;
5671
5672       case MachinePlaysWhite:
5673         /* User is moving for Black */
5674         if (WhiteOnMove(currentMove)) {
5675             DisplayMoveError(_("It is White's turn"));
5676             return ImpossibleMove;
5677         }
5678         break;
5679
5680       case MachinePlaysBlack:
5681         /* User is moving for White */
5682         if (!WhiteOnMove(currentMove)) {
5683             DisplayMoveError(_("It is Black's turn"));
5684             return ImpossibleMove;
5685         }
5686         break;
5687
5688       case EditGame:
5689       case IcsExamining:
5690       case BeginningOfGame:
5691       case AnalyzeMode:
5692       case Training:
5693         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5694             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5695             /* User is moving for Black */
5696             if (WhiteOnMove(currentMove)) {
5697                 DisplayMoveError(_("It is White's turn"));
5698                 return ImpossibleMove;
5699             }
5700         } else {
5701             /* User is moving for White */
5702             if (!WhiteOnMove(currentMove)) {
5703                 DisplayMoveError(_("It is Black's turn"));
5704                 return ImpossibleMove;
5705             }
5706         }
5707         break;
5708
5709       case IcsPlayingBlack:
5710         /* User is moving for Black */
5711         if (WhiteOnMove(currentMove)) {
5712             if (!appData.premove) {
5713                 DisplayMoveError(_("It is White's turn"));
5714             } else if (toX >= 0 && toY >= 0) {
5715                 premoveToX = toX;
5716                 premoveToY = toY;
5717                 premoveFromX = fromX;
5718                 premoveFromY = fromY;
5719                 premovePromoChar = promoChar;
5720                 gotPremove = 1;
5721                 if (appData.debugMode) 
5722                     fprintf(debugFP, "Got premove: fromX %d,"
5723                             "fromY %d, toX %d, toY %d\n",
5724                             fromX, fromY, toX, toY);
5725             }
5726             return ImpossibleMove;
5727         }
5728         break;
5729
5730       case IcsPlayingWhite:
5731         /* User is moving for White */
5732         if (!WhiteOnMove(currentMove)) {
5733             if (!appData.premove) {
5734                 DisplayMoveError(_("It is Black's turn"));
5735             } else if (toX >= 0 && toY >= 0) {
5736                 premoveToX = toX;
5737                 premoveToY = toY;
5738                 premoveFromX = fromX;
5739                 premoveFromY = fromY;
5740                 premovePromoChar = promoChar;
5741                 gotPremove = 1;
5742                 if (appData.debugMode) 
5743                     fprintf(debugFP, "Got premove: fromX %d,"
5744                             "fromY %d, toX %d, toY %d\n",
5745                             fromX, fromY, toX, toY);
5746             }
5747             return ImpossibleMove;
5748         }
5749         break;
5750
5751       default:
5752         break;
5753
5754       case EditPosition:
5755         /* EditPosition, empty square, or different color piece;
5756            click-click move is possible */
5757         if (toX == -2 || toY == -2) {
5758             boards[0][fromY][fromX] = EmptySquare;
5759             return AmbiguousMove;
5760         } else if (toX >= 0 && toY >= 0) {
5761             boards[0][toY][toX] = boards[0][fromY][fromX];
5762             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5763                 if(boards[0][fromY][0] != EmptySquare) {
5764                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5765                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5766                 }
5767             } else
5768             if(fromX == BOARD_RGHT+1) {
5769                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5770                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5771                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5772                 }
5773             } else
5774             boards[0][fromY][fromX] = EmptySquare;
5775             return AmbiguousMove;
5776         }
5777         return ImpossibleMove;
5778     }
5779
5780     if(toX < 0 || toY < 0) return ImpossibleMove;
5781     pdown = boards[currentMove][fromY][fromX];
5782     pup = boards[currentMove][toY][toX];
5783
5784     /* [HGM] If move started in holdings, it means a drop */
5785     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5786          if( pup != EmptySquare ) return ImpossibleMove;
5787          if(appData.testLegality) {
5788              /* it would be more logical if LegalityTest() also figured out
5789               * which drops are legal. For now we forbid pawns on back rank.
5790               * Shogi is on its own here...
5791               */
5792              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5793                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5794                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5795          }
5796          return WhiteDrop; /* Not needed to specify white or black yet */
5797     }
5798
5799     /* [HGM] always test for legality, to get promotion info */
5800     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5801                                          fromY, fromX, toY, toX, promoChar);
5802     /* [HGM] but possibly ignore an IllegalMove result */
5803     if (appData.testLegality) {
5804         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5805             DisplayMoveError(_("Illegal move"));
5806             return ImpossibleMove;
5807         }
5808     }
5809
5810     return moveType;
5811     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5812        function is made into one that returns an OK move type if FinishMove
5813        should be called. This to give the calling driver routine the
5814        opportunity to finish the userMove input with a promotion popup,
5815        without bothering the user with this for invalid or illegal moves */
5816
5817 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5818 }
5819
5820 /* Common tail of UserMoveEvent and DropMenuEvent */
5821 int
5822 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5823      ChessMove moveType;
5824      int fromX, fromY, toX, toY;
5825      /*char*/int promoChar;
5826 {
5827     char *bookHit = 0;
5828
5829     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5830         // [HGM] superchess: suppress promotions to non-available piece
5831         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5832         if(WhiteOnMove(currentMove)) {
5833             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5834         } else {
5835             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5836         }
5837     }
5838
5839     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5840        move type in caller when we know the move is a legal promotion */
5841     if(moveType == NormalMove && promoChar)
5842         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5843
5844     /* [HGM] convert drag-and-drop piece drops to standard form */
5845     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5846          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5847            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5848                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5849            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5850            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5851            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5852            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5853          fromY = DROP_RANK;
5854     }
5855
5856     /* [HGM] <popupFix> The following if has been moved here from
5857        UserMoveEvent(). Because it seemed to belong here (why not allow
5858        piece drops in training games?), and because it can only be
5859        performed after it is known to what we promote. */
5860     if (gameMode == Training) {
5861       /* compare the move played on the board to the next move in the
5862        * game. If they match, display the move and the opponent's response. 
5863        * If they don't match, display an error message.
5864        */
5865       int saveAnimate;
5866       Board testBoard;
5867       CopyBoard(testBoard, boards[currentMove]);
5868       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5869
5870       if (CompareBoards(testBoard, boards[currentMove+1])) {
5871         ForwardInner(currentMove+1);
5872
5873         /* Autoplay the opponent's response.
5874          * if appData.animate was TRUE when Training mode was entered,
5875          * the response will be animated.
5876          */
5877         saveAnimate = appData.animate;
5878         appData.animate = animateTraining;
5879         ForwardInner(currentMove+1);
5880         appData.animate = saveAnimate;
5881
5882         /* check for the end of the game */
5883         if (currentMove >= forwardMostMove) {
5884           gameMode = PlayFromGameFile;
5885           ModeHighlight();
5886           SetTrainingModeOff();
5887           DisplayInformation(_("End of game"));
5888         }
5889       } else {
5890         DisplayError(_("Incorrect move"), 0);
5891       }
5892       return 1;
5893     }
5894
5895   /* Ok, now we know that the move is good, so we can kill
5896      the previous line in Analysis Mode */
5897   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5898                                 && currentMove < forwardMostMove) {
5899     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5900   }
5901
5902   /* If we need the chess program but it's dead, restart it */
5903   ResurrectChessProgram();
5904
5905   /* A user move restarts a paused game*/
5906   if (pausing)
5907     PauseEvent();
5908
5909   thinkOutput[0] = NULLCHAR;
5910
5911   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5912
5913   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5914
5915   if (gameMode == BeginningOfGame) {
5916     if (appData.noChessProgram) {
5917       gameMode = EditGame;
5918       SetGameInfo();
5919     } else {
5920       char buf[MSG_SIZ];
5921       gameMode = MachinePlaysBlack;
5922       StartClocks();
5923       SetGameInfo();
5924       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5925       DisplayTitle(buf);
5926       if (first.sendName) {
5927         sprintf(buf, "name %s\n", gameInfo.white);
5928         SendToProgram(buf, &first);
5929       }
5930       StartClocks();
5931     }
5932     ModeHighlight();
5933   }
5934
5935   /* Relay move to ICS or chess engine */
5936   if (appData.icsActive) {
5937     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5938         gameMode == IcsExamining) {
5939       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5940         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5941         SendToICS("draw ");
5942         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5943       }
5944       // also send plain move, in case ICS does not understand atomic claims
5945       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5946       ics_user_moved = 1;
5947     }
5948   } else {
5949     if (first.sendTime && (gameMode == BeginningOfGame ||
5950                            gameMode == MachinePlaysWhite ||
5951                            gameMode == MachinePlaysBlack)) {
5952       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5953     }
5954     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5955          // [HGM] book: if program might be playing, let it use book
5956         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5957         first.maybeThinking = TRUE;
5958     } else SendMoveToProgram(forwardMostMove-1, &first);
5959     if (currentMove == cmailOldMove + 1) {
5960       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5961     }
5962   }
5963
5964   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5965
5966   switch (gameMode) {
5967   case EditGame:
5968     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5969     case MT_NONE:
5970     case MT_CHECK:
5971       break;
5972     case MT_CHECKMATE:
5973     case MT_STAINMATE:
5974       if (WhiteOnMove(currentMove)) {
5975         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5976       } else {
5977         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5978       }
5979       break;
5980     case MT_STALEMATE:
5981       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5982       break;
5983     }
5984     break;
5985     
5986   case MachinePlaysBlack:
5987   case MachinePlaysWhite:
5988     /* disable certain menu options while machine is thinking */
5989     SetMachineThinkingEnables();
5990     break;
5991
5992   default:
5993     break;
5994   }
5995
5996   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5997         
5998   if(bookHit) { // [HGM] book: simulate book reply
5999         static char bookMove[MSG_SIZ]; // a bit generous?
6000
6001         programStats.nodes = programStats.depth = programStats.time = 
6002         programStats.score = programStats.got_only_move = 0;
6003         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6004
6005         strcpy(bookMove, "move ");
6006         strcat(bookMove, bookHit);
6007         HandleMachineMove(bookMove, &first);
6008   }
6009   return 1;
6010 }
6011
6012 void
6013 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6014      int fromX, fromY, toX, toY;
6015      int promoChar;
6016 {
6017     /* [HGM] This routine was added to allow calling of its two logical
6018        parts from other modules in the old way. Before, UserMoveEvent()
6019        automatically called FinishMove() if the move was OK, and returned
6020        otherwise. I separated the two, in order to make it possible to
6021        slip a promotion popup in between. But that it always needs two
6022        calls, to the first part, (now called UserMoveTest() ), and to
6023        FinishMove if the first part succeeded. Calls that do not need
6024        to do anything in between, can call this routine the old way. 
6025     */
6026     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6027 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6028     if(moveType == AmbiguousMove)
6029         DrawPosition(FALSE, boards[currentMove]);
6030     else if(moveType != ImpossibleMove && moveType != Comment)
6031         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6032 }
6033
6034 void
6035 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6036      Board board;
6037      int flags;
6038      ChessMove kind;
6039      int rf, ff, rt, ft;
6040      VOIDSTAR closure;
6041 {
6042     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6043     Markers *m = (Markers *) closure;
6044     if(rf == fromY && ff == fromX)
6045         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6046                          || kind == WhiteCapturesEnPassant
6047                          || kind == BlackCapturesEnPassant);
6048     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6049 }
6050
6051 void
6052 MarkTargetSquares(int clear)
6053 {
6054   int x, y;
6055   if(!appData.markers || !appData.highlightDragging || 
6056      !appData.testLegality || gameMode == EditPosition) return;
6057   if(clear) {
6058     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6059   } else {
6060     int capt = 0;
6061     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6062     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6063       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6064       if(capt)
6065       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6066     }
6067   }
6068   DrawPosition(TRUE, NULL);
6069 }
6070
6071 void LeftClick(ClickType clickType, int xPix, int yPix)
6072 {
6073     int x, y;
6074     Boolean saveAnimate;
6075     static int second = 0, promotionChoice = 0;
6076     char promoChoice = NULLCHAR;
6077
6078     if(appData.seekGraph && appData.icsActive && loggedOn &&
6079         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6080         SeekGraphClick(clickType, xPix, yPix, 0);
6081         return;
6082     }
6083
6084     if (clickType == Press) ErrorPopDown();
6085     MarkTargetSquares(1);
6086
6087     x = EventToSquare(xPix, BOARD_WIDTH);
6088     y = EventToSquare(yPix, BOARD_HEIGHT);
6089     if (!flipView && y >= 0) {
6090         y = BOARD_HEIGHT - 1 - y;
6091     }
6092     if (flipView && x >= 0) {
6093         x = BOARD_WIDTH - 1 - x;
6094     }
6095
6096     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6097         if(clickType == Release) return; // ignore upclick of click-click destination
6098         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6099         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6100         if(gameInfo.holdingsWidth && 
6101                 (WhiteOnMove(currentMove) 
6102                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6103                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6104             // click in right holdings, for determining promotion piece
6105             ChessSquare p = boards[currentMove][y][x];
6106             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6107             if(p != EmptySquare) {
6108                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6109                 fromX = fromY = -1;
6110                 return;
6111             }
6112         }
6113         DrawPosition(FALSE, boards[currentMove]);
6114         return;
6115     }
6116
6117     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6118     if(clickType == Press
6119             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6120               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6121               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6122         return;
6123
6124     autoQueen = appData.alwaysPromoteToQueen;
6125
6126     if (fromX == -1) {
6127       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6128         if (clickType == Press) {
6129             /* First square */
6130             if (OKToStartUserMove(x, y)) {
6131                 fromX = x;
6132                 fromY = y;
6133                 second = 0;
6134                 MarkTargetSquares(0);
6135                 DragPieceBegin(xPix, yPix);
6136                 if (appData.highlightDragging) {
6137                     SetHighlights(x, y, -1, -1);
6138                 }
6139             }
6140         }
6141         return;
6142       }
6143     }
6144
6145     /* fromX != -1 */
6146     if (clickType == Press && gameMode != EditPosition) {
6147         ChessSquare fromP;
6148         ChessSquare toP;
6149         int frc;
6150
6151         // ignore off-board to clicks
6152         if(y < 0 || x < 0) return;
6153
6154         /* Check if clicking again on the same color piece */
6155         fromP = boards[currentMove][fromY][fromX];
6156         toP = boards[currentMove][y][x];
6157         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6158         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6159              WhitePawn <= toP && toP <= WhiteKing &&
6160              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6161              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6162             (BlackPawn <= fromP && fromP <= BlackKing && 
6163              BlackPawn <= toP && toP <= BlackKing &&
6164              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6165              !(fromP == BlackKing && toP == BlackRook && frc))) {
6166             /* Clicked again on same color piece -- changed his mind */
6167             second = (x == fromX && y == fromY);
6168            if(!second || !OnlyMove(&x, &y, TRUE)) {
6169             if (appData.highlightDragging) {
6170                 SetHighlights(x, y, -1, -1);
6171             } else {
6172                 ClearHighlights();
6173             }
6174             if (OKToStartUserMove(x, y)) {
6175                 fromX = x;
6176                 fromY = y;
6177                 MarkTargetSquares(0);
6178                 DragPieceBegin(xPix, yPix);
6179             }
6180             return;
6181            }
6182         }
6183         // ignore clicks on holdings
6184         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6185     }
6186
6187     if (clickType == Release && x == fromX && y == fromY) {
6188         DragPieceEnd(xPix, yPix);
6189         if (appData.animateDragging) {
6190             /* Undo animation damage if any */
6191             DrawPosition(FALSE, NULL);
6192         }
6193         if (second) {
6194             /* Second up/down in same square; just abort move */
6195             second = 0;
6196             fromX = fromY = -1;
6197             ClearHighlights();
6198             gotPremove = 0;
6199             ClearPremoveHighlights();
6200         } else {
6201             /* First upclick in same square; start click-click mode */
6202             SetHighlights(x, y, -1, -1);
6203         }
6204         return;
6205     }
6206
6207     /* we now have a different from- and (possibly off-board) to-square */
6208     /* Completed move */
6209     toX = x;
6210     toY = y;
6211     saveAnimate = appData.animate;
6212     if (clickType == Press) {
6213         /* Finish clickclick move */
6214         if (appData.animate || appData.highlightLastMove) {
6215             SetHighlights(fromX, fromY, toX, toY);
6216         } else {
6217             ClearHighlights();
6218         }
6219     } else {
6220         /* Finish drag move */
6221         if (appData.highlightLastMove) {
6222             SetHighlights(fromX, fromY, toX, toY);
6223         } else {
6224             ClearHighlights();
6225         }
6226         DragPieceEnd(xPix, yPix);
6227         /* Don't animate move and drag both */
6228         appData.animate = FALSE;
6229     }
6230
6231     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6232     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6233         ChessSquare piece = boards[currentMove][fromY][fromX];
6234         if(gameMode == EditPosition && piece != EmptySquare &&
6235            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6236             int n;
6237              
6238             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6239                 n = PieceToNumber(piece - (int)BlackPawn);
6240                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6241                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6242                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6243             } else
6244             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6245                 n = PieceToNumber(piece);
6246                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6247                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6248                 boards[currentMove][n][BOARD_WIDTH-2]++;
6249             }
6250             boards[currentMove][fromY][fromX] = EmptySquare;
6251         }
6252         ClearHighlights();
6253         fromX = fromY = -1;
6254         DrawPosition(TRUE, boards[currentMove]);
6255         return;
6256     }
6257
6258     // off-board moves should not be highlighted
6259     if(x < 0 || x < 0) ClearHighlights();
6260
6261     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6262         SetHighlights(fromX, fromY, toX, toY);
6263         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6264             // [HGM] super: promotion to captured piece selected from holdings
6265             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6266             promotionChoice = TRUE;
6267             // kludge follows to temporarily execute move on display, without promoting yet
6268             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6269             boards[currentMove][toY][toX] = p;
6270             DrawPosition(FALSE, boards[currentMove]);
6271             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6272             boards[currentMove][toY][toX] = q;
6273             DisplayMessage("Click in holdings to choose piece", "");
6274             return;
6275         }
6276         PromotionPopUp();
6277     } else {
6278         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6279         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6280         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6281         fromX = fromY = -1;
6282     }
6283     appData.animate = saveAnimate;
6284     if (appData.animate || appData.animateDragging) {
6285         /* Undo animation damage if needed */
6286         DrawPosition(FALSE, NULL);
6287     }
6288 }
6289
6290 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6291 {   // front-end-free part taken out of PieceMenuPopup
6292     int whichMenu; int xSqr, ySqr;
6293
6294     if(seekGraphUp) { // [HGM] seekgraph
6295         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6296         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6297         return -2;
6298     }
6299
6300     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6301          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6302         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6303         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6304         return -2;
6305     }
6306
6307     xSqr = EventToSquare(x, BOARD_WIDTH);
6308     ySqr = EventToSquare(y, BOARD_HEIGHT);
6309     if (action == Release) UnLoadPV(); // [HGM] pv
6310     if (action != Press) return -2; // return code to be ignored
6311     switch (gameMode) {
6312       case IcsExamining:
6313         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6314       case EditPosition:
6315         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6316         if (xSqr < 0 || ySqr < 0) return -1;\r
6317         whichMenu = 0; // edit-position menu
6318         break;
6319       case IcsObserving:
6320         if(!appData.icsEngineAnalyze) return -1;
6321       case IcsPlayingWhite:
6322       case IcsPlayingBlack:
6323         if(!appData.zippyPlay) goto noZip;
6324       case AnalyzeMode:
6325       case AnalyzeFile:
6326       case MachinePlaysWhite:
6327       case MachinePlaysBlack:
6328       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6329         if (!appData.dropMenu) {
6330           LoadPV(x, y);
6331           return 2; // flag front-end to grab mouse events
6332         }
6333         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6334            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6335       case EditGame:
6336       noZip:
6337         if (xSqr < 0 || ySqr < 0) return -1;
6338         if (!appData.dropMenu || appData.testLegality &&
6339             gameInfo.variant != VariantBughouse &&
6340             gameInfo.variant != VariantCrazyhouse) return -1;
6341         whichMenu = 1; // drop menu
6342         break;
6343       default:
6344         return -1;
6345     }
6346
6347     if (((*fromX = xSqr) < 0) ||
6348         ((*fromY = ySqr) < 0)) {
6349         *fromX = *fromY = -1;
6350         return -1;
6351     }
6352     if (flipView)
6353       *fromX = BOARD_WIDTH - 1 - *fromX;
6354     else
6355       *fromY = BOARD_HEIGHT - 1 - *fromY;
6356
6357     return whichMenu;
6358 }
6359
6360 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6361 {
6362 //    char * hint = lastHint;
6363     FrontEndProgramStats stats;
6364
6365     stats.which = cps == &first ? 0 : 1;
6366     stats.depth = cpstats->depth;
6367     stats.nodes = cpstats->nodes;
6368     stats.score = cpstats->score;
6369     stats.time = cpstats->time;
6370     stats.pv = cpstats->movelist;
6371     stats.hint = lastHint;
6372     stats.an_move_index = 0;
6373     stats.an_move_count = 0;
6374
6375     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6376         stats.hint = cpstats->move_name;
6377         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6378         stats.an_move_count = cpstats->nr_moves;
6379     }
6380
6381     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6382
6383     SetProgramStats( &stats );
6384 }
6385
6386 int
6387 Adjudicate(ChessProgramState *cps)
6388 {       // [HGM] some adjudications useful with buggy engines
6389         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6390         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6391         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6392         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6393         int k, count = 0; static int bare = 1;
6394         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6395         Boolean canAdjudicate = !appData.icsActive;
6396
6397         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6398         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6399             if( appData.testLegality )
6400             {   /* [HGM] Some more adjudications for obstinate engines */
6401                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6402                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6403                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6404                 static int moveCount = 6;
6405                 ChessMove result;
6406                 char *reason = NULL;
6407
6408                 /* Count what is on board. */
6409                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6410                 {   ChessSquare p = boards[forwardMostMove][i][j];
6411                     int m=i;
6412
6413                     switch((int) p)
6414                     {   /* count B,N,R and other of each side */
6415                         case WhiteKing:
6416                         case BlackKing:
6417                              NrK++; break; // [HGM] atomic: count Kings
6418                         case WhiteKnight:
6419                              NrWN++; break;
6420                         case WhiteBishop:
6421                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6422                              bishopsColor |= 1 << ((i^j)&1);
6423                              NrWB++; break;
6424                         case BlackKnight:
6425                              NrBN++; break;
6426                         case BlackBishop:
6427                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6428                              bishopsColor |= 1 << ((i^j)&1);
6429                              NrBB++; break;
6430                         case WhiteRook:
6431                              NrWR++; break;
6432                         case BlackRook:
6433                              NrBR++; break;
6434                         case WhiteQueen:
6435                              NrWQ++; break;
6436                         case BlackQueen:
6437                              NrBQ++; break;
6438                         case EmptySquare: 
6439                              break;
6440                         case BlackPawn:
6441                              m = 7-i;
6442                         case WhitePawn:
6443                              PawnAdvance += m; NrPawns++;
6444                     }
6445                     NrPieces += (p != EmptySquare);
6446                     NrW += ((int)p < (int)BlackPawn);
6447                     if(gameInfo.variant == VariantXiangqi && 
6448                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6449                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6450                         NrW -= ((int)p < (int)BlackPawn);
6451                     }
6452                 }
6453
6454                 /* Some material-based adjudications that have to be made before stalemate test */
6455                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6456                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6457                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6458                      if(canAdjudicate && appData.checkMates) {
6459                          if(engineOpponent)
6460                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6461                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6462                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6463                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6464                          return 1;
6465                      }
6466                 }
6467
6468                 /* Bare King in Shatranj (loses) or Losers (wins) */
6469                 if( NrW == 1 || NrPieces - NrW == 1) {
6470                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6471                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6472                      if(canAdjudicate && appData.checkMates) {
6473                          if(engineOpponent)
6474                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6475                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6476                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6477                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6478                          return 1;
6479                      }
6480                   } else
6481                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6482                   {    /* bare King */
6483                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6484                         if(canAdjudicate && appData.checkMates) {
6485                             /* but only adjudicate if adjudication enabled */
6486                             if(engineOpponent)
6487                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6488                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6489                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6490                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6491                             return 1;
6492                         }
6493                   }
6494                 } else bare = 1;
6495
6496
6497             // don't wait for engine to announce game end if we can judge ourselves
6498             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6499               case MT_CHECK:
6500                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6501                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6502                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6503                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6504                             checkCnt++;
6505                         if(checkCnt >= 2) {
6506                             reason = "Xboard adjudication: 3rd check";
6507                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6508                             break;
6509                         }
6510                     }
6511                 }
6512               case MT_NONE:
6513               default:
6514                 break;
6515               case MT_STALEMATE:
6516               case MT_STAINMATE:
6517                 reason = "Xboard adjudication: Stalemate";
6518                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6519                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6520                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6521                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6522                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6523                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6524                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6525                                                                         EP_CHECKMATE : EP_WINS);
6526                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6527                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6528                 }
6529                 break;
6530               case MT_CHECKMATE:
6531                 reason = "Xboard adjudication: Checkmate";
6532                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6533                 break;
6534             }
6535
6536                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6537                     case EP_STALEMATE:
6538                         result = GameIsDrawn; break;
6539                     case EP_CHECKMATE:
6540                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6541                     case EP_WINS:
6542                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6543                     default:
6544                         result = (ChessMove) 0;
6545                 }
6546                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6547                     if(engineOpponent)
6548                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6549                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6550                     GameEnds( result, reason, GE_XBOARD );
6551                     return 1;
6552                 }
6553
6554                 /* Next absolutely insufficient mating material. */
6555                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6556                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6557                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6558                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6559                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6560
6561                      /* always flag draws, for judging claims */
6562                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6563
6564                      if(canAdjudicate && appData.materialDraws) {
6565                          /* but only adjudicate them if adjudication enabled */
6566                          if(engineOpponent) {
6567                            SendToProgram("force\n", engineOpponent); // suppress reply
6568                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6569                          }
6570                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6571                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6572                          return 1;
6573                      }
6574                 }
6575
6576                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6577                 if(NrPieces == 4 && 
6578                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6579                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6580                    || NrWN==2 || NrBN==2     /* KNNK */
6581                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6582                   ) ) {
6583                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6584                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6585                           if(engineOpponent) {
6586                             SendToProgram("force\n", engineOpponent); // suppress reply
6587                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6588                           }
6589                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6590                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6591                           return 1;
6592                      }
6593                 } else moveCount = 6;
6594             }
6595         }
6596           
6597         if (appData.debugMode) { int i;
6598             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6599                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6600                     appData.drawRepeats);
6601             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6602               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6603             
6604         }
6605
6606         // Repetition draws and 50-move rule can be applied independently of legality testing
6607
6608                 /* Check for rep-draws */
6609                 count = 0;
6610                 for(k = forwardMostMove-2;
6611                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6612                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6613                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6614                     k-=2)
6615                 {   int rights=0;
6616                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6617                         /* compare castling rights */
6618                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6619                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6620                                 rights++; /* King lost rights, while rook still had them */
6621                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6622                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6623                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6624                                    rights++; /* but at least one rook lost them */
6625                         }
6626                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6627                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6628                                 rights++; 
6629                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6630                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6631                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6632                                    rights++;
6633                         }
6634                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6635                             && appData.drawRepeats > 1) {
6636                              /* adjudicate after user-specified nr of repeats */
6637                              if(engineOpponent) {
6638                                SendToProgram("force\n", engineOpponent); // suppress reply
6639                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6640                              }
6641                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6642                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6643                                 // [HGM] xiangqi: check for forbidden perpetuals
6644                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6645                                 for(m=forwardMostMove; m>k; m-=2) {
6646                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6647                                         ourPerpetual = 0; // the current mover did not always check
6648                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6649                                         hisPerpetual = 0; // the opponent did not always check
6650                                 }
6651                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6652                                                                         ourPerpetual, hisPerpetual);
6653                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6654                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6655                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6656                                     return 1;
6657                                 }
6658                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6659                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6660                                 // Now check for perpetual chases
6661                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6662                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6663                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6664                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6665                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6666                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6667                                         return 1;
6668                                     }
6669                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6670                                         break; // Abort repetition-checking loop.
6671                                 }
6672                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6673                              }
6674                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6675                              return 1;
6676                         }
6677                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6678                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6679                     }
6680                 }
6681
6682                 /* Now we test for 50-move draws. Determine ply count */
6683                 count = forwardMostMove;
6684                 /* look for last irreversble move */
6685                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6686                     count--;
6687                 /* if we hit starting position, add initial plies */
6688                 if( count == backwardMostMove )
6689                     count -= initialRulePlies;
6690                 count = forwardMostMove - count; 
6691                 if( count >= 100)
6692                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6693                          /* this is used to judge if draw claims are legal */
6694                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6695                          if(engineOpponent) {
6696                            SendToProgram("force\n", engineOpponent); // suppress reply
6697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6698                          }
6699                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6700                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6701                          return 1;
6702                 }
6703
6704                 /* if draw offer is pending, treat it as a draw claim
6705                  * when draw condition present, to allow engines a way to
6706                  * claim draws before making their move to avoid a race
6707                  * condition occurring after their move
6708                  */
6709                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6710                          char *p = NULL;
6711                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6712                              p = "Draw claim: 50-move rule";
6713                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6714                              p = "Draw claim: 3-fold repetition";
6715                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6716                              p = "Draw claim: insufficient mating material";
6717                          if( p != NULL && canAdjudicate) {
6718                              if(engineOpponent) {
6719                                SendToProgram("force\n", engineOpponent); // suppress reply
6720                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6721                              }
6722                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6723                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6724                              return 1;
6725                          }
6726                 }
6727
6728                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6729                     if(engineOpponent) {
6730                       SendToProgram("force\n", engineOpponent); // suppress reply
6731                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6732                     }
6733                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6734                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6735                     return 1;
6736                 }
6737         return 0;
6738 }
6739
6740 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6741 {   // [HGM] book: this routine intercepts moves to simulate book replies
6742     char *bookHit = NULL;
6743
6744     //first determine if the incoming move brings opponent into his book
6745     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6746         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6747     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6748     if(bookHit != NULL && !cps->bookSuspend) {
6749         // make sure opponent is not going to reply after receiving move to book position
6750         SendToProgram("force\n", cps);
6751         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6752     }
6753     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6754     // now arrange restart after book miss
6755     if(bookHit) {
6756         // after a book hit we never send 'go', and the code after the call to this routine
6757         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6758         char buf[MSG_SIZ];
6759         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6760         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6761         SendToProgram(buf, cps);
6762         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6763     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6764         SendToProgram("go\n", cps);
6765         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6766     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6767         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6768             SendToProgram("go\n", cps); 
6769         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6770     }
6771     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6772 }
6773
6774 char *savedMessage;
6775 ChessProgramState *savedState;
6776 void DeferredBookMove(void)
6777 {
6778         if(savedState->lastPing != savedState->lastPong)
6779                     ScheduleDelayedEvent(DeferredBookMove, 10);
6780         else
6781         HandleMachineMove(savedMessage, savedState);
6782 }
6783
6784 void
6785 HandleMachineMove(message, cps)
6786      char *message;
6787      ChessProgramState *cps;
6788 {
6789     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6790     char realname[MSG_SIZ];
6791     int fromX, fromY, toX, toY;
6792     ChessMove moveType;
6793     char promoChar;
6794     char *p;
6795     int machineWhite;
6796     char *bookHit;
6797
6798     cps->userError = 0;
6799
6800 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6801     /*
6802      * Kludge to ignore BEL characters
6803      */
6804     while (*message == '\007') message++;
6805
6806     /*
6807      * [HGM] engine debug message: ignore lines starting with '#' character
6808      */
6809     if(cps->debug && *message == '#') return;
6810
6811     /*
6812      * Look for book output
6813      */
6814     if (cps == &first && bookRequested) {
6815         if (message[0] == '\t' || message[0] == ' ') {
6816             /* Part of the book output is here; append it */
6817             strcat(bookOutput, message);
6818             strcat(bookOutput, "  \n");
6819             return;
6820         } else if (bookOutput[0] != NULLCHAR) {
6821             /* All of book output has arrived; display it */
6822             char *p = bookOutput;
6823             while (*p != NULLCHAR) {
6824                 if (*p == '\t') *p = ' ';
6825                 p++;
6826             }
6827             DisplayInformation(bookOutput);
6828             bookRequested = FALSE;
6829             /* Fall through to parse the current output */
6830         }
6831     }
6832
6833     /*
6834      * Look for machine move.
6835      */
6836     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6837         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6838     {
6839         /* This method is only useful on engines that support ping */
6840         if (cps->lastPing != cps->lastPong) {
6841           if (gameMode == BeginningOfGame) {
6842             /* Extra move from before last new; ignore */
6843             if (appData.debugMode) {
6844                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6845             }
6846           } else {
6847             if (appData.debugMode) {
6848                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6849                         cps->which, gameMode);
6850             }
6851
6852             SendToProgram("undo\n", cps);
6853           }
6854           return;
6855         }
6856
6857         switch (gameMode) {
6858           case BeginningOfGame:
6859             /* Extra move from before last reset; ignore */
6860             if (appData.debugMode) {
6861                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6862             }
6863             return;
6864
6865           case EndOfGame:
6866           case IcsIdle:
6867           default:
6868             /* Extra move after we tried to stop.  The mode test is
6869                not a reliable way of detecting this problem, but it's
6870                the best we can do on engines that don't support ping.
6871             */
6872             if (appData.debugMode) {
6873                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6874                         cps->which, gameMode);
6875             }
6876             SendToProgram("undo\n", cps);
6877             return;
6878
6879           case MachinePlaysWhite:
6880           case IcsPlayingWhite:
6881             machineWhite = TRUE;
6882             break;
6883
6884           case MachinePlaysBlack:
6885           case IcsPlayingBlack:
6886             machineWhite = FALSE;
6887             break;
6888
6889           case TwoMachinesPlay:
6890             machineWhite = (cps->twoMachinesColor[0] == 'w');
6891             break;
6892         }
6893         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6894             if (appData.debugMode) {
6895                 fprintf(debugFP,
6896                         "Ignoring move out of turn by %s, gameMode %d"
6897                         ", forwardMost %d\n",
6898                         cps->which, gameMode, forwardMostMove);
6899             }
6900             return;
6901         }
6902
6903     if (appData.debugMode) { int f = forwardMostMove;
6904         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6905                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6906                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6907     }
6908         if(cps->alphaRank) AlphaRank(machineMove, 4);
6909         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6910                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6911             /* Machine move could not be parsed; ignore it. */
6912             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6913                     machineMove, cps->which);
6914             DisplayError(buf1, 0);
6915             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6916                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6917             if (gameMode == TwoMachinesPlay) {
6918               GameEnds(machineWhite ? BlackWins : WhiteWins,
6919                        buf1, GE_XBOARD);
6920             }
6921             return;
6922         }
6923
6924         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6925         /* So we have to redo legality test with true e.p. status here,  */
6926         /* to make sure an illegal e.p. capture does not slip through,   */
6927         /* to cause a forfeit on a justified illegal-move complaint      */
6928         /* of the opponent.                                              */
6929         if( gameMode==TwoMachinesPlay && appData.testLegality
6930             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6931                                                               ) {
6932            ChessMove moveType;
6933            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6934                              fromY, fromX, toY, toX, promoChar);
6935             if (appData.debugMode) {
6936                 int i;
6937                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6938                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6939                 fprintf(debugFP, "castling rights\n");
6940             }
6941             if(moveType == IllegalMove) {
6942                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6943                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6944                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6945                            buf1, GE_XBOARD);
6946                 return;
6947            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6948            /* [HGM] Kludge to handle engines that send FRC-style castling
6949               when they shouldn't (like TSCP-Gothic) */
6950            switch(moveType) {
6951              case WhiteASideCastleFR:
6952              case BlackASideCastleFR:
6953                toX+=2;
6954                currentMoveString[2]++;
6955                break;
6956              case WhiteHSideCastleFR:
6957              case BlackHSideCastleFR:
6958                toX--;
6959                currentMoveString[2]--;
6960                break;
6961              default: ; // nothing to do, but suppresses warning of pedantic compilers
6962            }
6963         }
6964         hintRequested = FALSE;
6965         lastHint[0] = NULLCHAR;
6966         bookRequested = FALSE;
6967         /* Program may be pondering now */
6968         cps->maybeThinking = TRUE;
6969         if (cps->sendTime == 2) cps->sendTime = 1;
6970         if (cps->offeredDraw) cps->offeredDraw--;
6971
6972         /* currentMoveString is set as a side-effect of ParseOneMove */
6973         strcpy(machineMove, currentMoveString);
6974         strcat(machineMove, "\n");
6975         strcpy(moveList[forwardMostMove], machineMove);
6976
6977         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6978
6979         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6980         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6981             int count = 0;
6982
6983             while( count < adjudicateLossPlies ) {
6984                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6985
6986                 if( count & 1 ) {
6987                     score = -score; /* Flip score for winning side */
6988                 }
6989
6990                 if( score > adjudicateLossThreshold ) {
6991                     break;
6992                 }
6993
6994                 count++;
6995             }
6996
6997             if( count >= adjudicateLossPlies ) {
6998                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6999
7000                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7001                     "Xboard adjudication", 
7002                     GE_XBOARD );
7003
7004                 return;
7005             }
7006         }
7007
7008         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7009
7010 #if ZIPPY
7011         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7012             first.initDone) {
7013           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7014                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7015                 SendToICS("draw ");
7016                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7017           }
7018           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7019           ics_user_moved = 1;
7020           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7021                 char buf[3*MSG_SIZ];
7022
7023                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7024                         programStats.score / 100.,
7025                         programStats.depth,
7026                         programStats.time / 100.,
7027                         (unsigned int)programStats.nodes,
7028                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7029                         programStats.movelist);
7030                 SendToICS(buf);
7031 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7032           }
7033         }
7034 #endif
7035
7036         /* [AS] Save move info and clear stats for next move */
7037         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7038         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7039         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7040         ClearProgramStats();
7041         thinkOutput[0] = NULLCHAR;
7042         hiddenThinkOutputState = 0;
7043
7044         bookHit = NULL;
7045         if (gameMode == TwoMachinesPlay) {
7046             /* [HGM] relaying draw offers moved to after reception of move */
7047             /* and interpreting offer as claim if it brings draw condition */
7048             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7049                 SendToProgram("draw\n", cps->other);
7050             }
7051             if (cps->other->sendTime) {
7052                 SendTimeRemaining(cps->other,
7053                                   cps->other->twoMachinesColor[0] == 'w');
7054             }
7055             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7056             if (firstMove && !bookHit) {
7057                 firstMove = FALSE;
7058                 if (cps->other->useColors) {
7059                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7060                 }
7061                 SendToProgram("go\n", cps->other);
7062             }
7063             cps->other->maybeThinking = TRUE;
7064         }
7065
7066         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7067         
7068         if (!pausing && appData.ringBellAfterMoves) {
7069             RingBell();
7070         }
7071
7072         /* 
7073          * Reenable menu items that were disabled while
7074          * machine was thinking
7075          */
7076         if (gameMode != TwoMachinesPlay)
7077             SetUserThinkingEnables();
7078
7079         // [HGM] book: after book hit opponent has received move and is now in force mode
7080         // force the book reply into it, and then fake that it outputted this move by jumping
7081         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7082         if(bookHit) {
7083                 static char bookMove[MSG_SIZ]; // a bit generous?
7084
7085                 strcpy(bookMove, "move ");
7086                 strcat(bookMove, bookHit);
7087                 message = bookMove;
7088                 cps = cps->other;
7089                 programStats.nodes = programStats.depth = programStats.time = 
7090                 programStats.score = programStats.got_only_move = 0;
7091                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7092
7093                 if(cps->lastPing != cps->lastPong) {
7094                     savedMessage = message; // args for deferred call
7095                     savedState = cps;
7096                     ScheduleDelayedEvent(DeferredBookMove, 10);
7097                     return;
7098                 }
7099                 goto FakeBookMove;
7100         }
7101
7102         return;
7103     }
7104
7105     /* Set special modes for chess engines.  Later something general
7106      *  could be added here; for now there is just one kludge feature,
7107      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7108      *  when "xboard" is given as an interactive command.
7109      */
7110     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7111         cps->useSigint = FALSE;
7112         cps->useSigterm = FALSE;
7113     }
7114     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7115       ParseFeatures(message+8, cps);
7116       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7117     }
7118
7119     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7120      * want this, I was asked to put it in, and obliged.
7121      */
7122     if (!strncmp(message, "setboard ", 9)) {
7123         Board initial_position;
7124
7125         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7126
7127         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7128             DisplayError(_("Bad FEN received from engine"), 0);
7129             return ;
7130         } else {
7131            Reset(TRUE, FALSE);
7132            CopyBoard(boards[0], initial_position);
7133            initialRulePlies = FENrulePlies;
7134            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7135            else gameMode = MachinePlaysBlack;                 
7136            DrawPosition(FALSE, boards[currentMove]);
7137         }
7138         return;
7139     }
7140
7141     /*
7142      * Look for communication commands
7143      */
7144     if (!strncmp(message, "telluser ", 9)) {
7145         DisplayNote(message + 9);
7146         return;
7147     }
7148     if (!strncmp(message, "tellusererror ", 14)) {
7149         cps->userError = 1;
7150         DisplayError(message + 14, 0);
7151         return;
7152     }
7153     if (!strncmp(message, "tellopponent ", 13)) {
7154       if (appData.icsActive) {
7155         if (loggedOn) {
7156           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7157           SendToICS(buf1);
7158         }
7159       } else {
7160         DisplayNote(message + 13);
7161       }
7162       return;
7163     }
7164     if (!strncmp(message, "tellothers ", 11)) {
7165       if (appData.icsActive) {
7166         if (loggedOn) {
7167           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7168           SendToICS(buf1);
7169         }
7170       }
7171       return;
7172     }
7173     if (!strncmp(message, "tellall ", 8)) {
7174       if (appData.icsActive) {
7175         if (loggedOn) {
7176           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7177           SendToICS(buf1);
7178         }
7179       } else {
7180         DisplayNote(message + 8);
7181       }
7182       return;
7183     }
7184     if (strncmp(message, "warning", 7) == 0) {
7185         /* Undocumented feature, use tellusererror in new code */
7186         DisplayError(message, 0);
7187         return;
7188     }
7189     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7190         strcpy(realname, cps->tidy);
7191         strcat(realname, " query");
7192         AskQuestion(realname, buf2, buf1, cps->pr);
7193         return;
7194     }
7195     /* Commands from the engine directly to ICS.  We don't allow these to be 
7196      *  sent until we are logged on. Crafty kibitzes have been known to 
7197      *  interfere with the login process.
7198      */
7199     if (loggedOn) {
7200         if (!strncmp(message, "tellics ", 8)) {
7201             SendToICS(message + 8);
7202             SendToICS("\n");
7203             return;
7204         }
7205         if (!strncmp(message, "tellicsnoalias ", 15)) {
7206             SendToICS(ics_prefix);
7207             SendToICS(message + 15);
7208             SendToICS("\n");
7209             return;
7210         }
7211         /* The following are for backward compatibility only */
7212         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7213             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7214             SendToICS(ics_prefix);
7215             SendToICS(message);
7216             SendToICS("\n");
7217             return;
7218         }
7219     }
7220     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7221         return;
7222     }
7223     /*
7224      * If the move is illegal, cancel it and redraw the board.
7225      * Also deal with other error cases.  Matching is rather loose
7226      * here to accommodate engines written before the spec.
7227      */
7228     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7229         strncmp(message, "Error", 5) == 0) {
7230         if (StrStr(message, "name") || 
7231             StrStr(message, "rating") || StrStr(message, "?") ||
7232             StrStr(message, "result") || StrStr(message, "board") ||
7233             StrStr(message, "bk") || StrStr(message, "computer") ||
7234             StrStr(message, "variant") || StrStr(message, "hint") ||
7235             StrStr(message, "random") || StrStr(message, "depth") ||
7236             StrStr(message, "accepted")) {
7237             return;
7238         }
7239         if (StrStr(message, "protover")) {
7240           /* Program is responding to input, so it's apparently done
7241              initializing, and this error message indicates it is
7242              protocol version 1.  So we don't need to wait any longer
7243              for it to initialize and send feature commands. */
7244           FeatureDone(cps, 1);
7245           cps->protocolVersion = 1;
7246           return;
7247         }
7248         cps->maybeThinking = FALSE;
7249
7250         if (StrStr(message, "draw")) {
7251             /* Program doesn't have "draw" command */
7252             cps->sendDrawOffers = 0;
7253             return;
7254         }
7255         if (cps->sendTime != 1 &&
7256             (StrStr(message, "time") || StrStr(message, "otim"))) {
7257           /* Program apparently doesn't have "time" or "otim" command */
7258           cps->sendTime = 0;
7259           return;
7260         }
7261         if (StrStr(message, "analyze")) {
7262             cps->analysisSupport = FALSE;
7263             cps->analyzing = FALSE;
7264             Reset(FALSE, TRUE);
7265             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7266             DisplayError(buf2, 0);
7267             return;
7268         }
7269         if (StrStr(message, "(no matching move)st")) {
7270           /* Special kludge for GNU Chess 4 only */
7271           cps->stKludge = TRUE;
7272           SendTimeControl(cps, movesPerSession, timeControl,
7273                           timeIncrement, appData.searchDepth,
7274                           searchTime);
7275           return;
7276         }
7277         if (StrStr(message, "(no matching move)sd")) {
7278           /* Special kludge for GNU Chess 4 only */
7279           cps->sdKludge = TRUE;
7280           SendTimeControl(cps, movesPerSession, timeControl,
7281                           timeIncrement, appData.searchDepth,
7282                           searchTime);
7283           return;
7284         }
7285         if (!StrStr(message, "llegal")) {
7286             return;
7287         }
7288         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7289             gameMode == IcsIdle) return;
7290         if (forwardMostMove <= backwardMostMove) return;
7291         if (pausing) PauseEvent();
7292       if(appData.forceIllegal) {
7293             // [HGM] illegal: machine refused move; force position after move into it
7294           SendToProgram("force\n", cps);
7295           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7296                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7297                 // when black is to move, while there might be nothing on a2 or black
7298                 // might already have the move. So send the board as if white has the move.
7299                 // But first we must change the stm of the engine, as it refused the last move
7300                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7301                 if(WhiteOnMove(forwardMostMove)) {
7302                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7303                     SendBoard(cps, forwardMostMove); // kludgeless board
7304                 } else {
7305                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7306                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7307                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7308                 }
7309           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7310             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7311                  gameMode == TwoMachinesPlay)
7312               SendToProgram("go\n", cps);
7313             return;
7314       } else
7315         if (gameMode == PlayFromGameFile) {
7316             /* Stop reading this game file */
7317             gameMode = EditGame;
7318             ModeHighlight();
7319         }
7320         currentMove = forwardMostMove-1;
7321         DisplayMove(currentMove-1); /* before DisplayMoveError */
7322         SwitchClocks(forwardMostMove-1); // [HGM] race
7323         DisplayBothClocks();
7324         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7325                 parseList[currentMove], cps->which);
7326         DisplayMoveError(buf1);
7327         DrawPosition(FALSE, boards[currentMove]);
7328
7329         /* [HGM] illegal-move claim should forfeit game when Xboard */
7330         /* only passes fully legal moves                            */
7331         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7332             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7333                                 "False illegal-move claim", GE_XBOARD );
7334         }
7335         return;
7336     }
7337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7338         /* Program has a broken "time" command that
7339            outputs a string not ending in newline.
7340            Don't use it. */
7341         cps->sendTime = 0;
7342     }
7343     
7344     /*
7345      * If chess program startup fails, exit with an error message.
7346      * Attempts to recover here are futile.
7347      */
7348     if ((StrStr(message, "unknown host") != NULL)
7349         || (StrStr(message, "No remote directory") != NULL)
7350         || (StrStr(message, "not found") != NULL)
7351         || (StrStr(message, "No such file") != NULL)
7352         || (StrStr(message, "can't alloc") != NULL)
7353         || (StrStr(message, "Permission denied") != NULL)) {
7354
7355         cps->maybeThinking = FALSE;
7356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7357                 cps->which, cps->program, cps->host, message);
7358         RemoveInputSource(cps->isr);
7359         DisplayFatalError(buf1, 0, 1);
7360         return;
7361     }
7362     
7363     /* 
7364      * Look for hint output
7365      */
7366     if (sscanf(message, "Hint: %s", buf1) == 1) {
7367         if (cps == &first && hintRequested) {
7368             hintRequested = FALSE;
7369             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7370                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7371                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7372                                     PosFlags(forwardMostMove),
7373                                     fromY, fromX, toY, toX, promoChar, buf1);
7374                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7375                 DisplayInformation(buf2);
7376             } else {
7377                 /* Hint move could not be parsed!? */
7378               snprintf(buf2, sizeof(buf2),
7379                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7380                         buf1, cps->which);
7381                 DisplayError(buf2, 0);
7382             }
7383         } else {
7384             strcpy(lastHint, buf1);
7385         }
7386         return;
7387     }
7388
7389     /*
7390      * Ignore other messages if game is not in progress
7391      */
7392     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7393         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7394
7395     /*
7396      * look for win, lose, draw, or draw offer
7397      */
7398     if (strncmp(message, "1-0", 3) == 0) {
7399         char *p, *q, *r = "";
7400         p = strchr(message, '{');
7401         if (p) {
7402             q = strchr(p, '}');
7403             if (q) {
7404                 *q = NULLCHAR;
7405                 r = p + 1;
7406             }
7407         }
7408         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7409         return;
7410     } else if (strncmp(message, "0-1", 3) == 0) {
7411         char *p, *q, *r = "";
7412         p = strchr(message, '{');
7413         if (p) {
7414             q = strchr(p, '}');
7415             if (q) {
7416                 *q = NULLCHAR;
7417                 r = p + 1;
7418             }
7419         }
7420         /* Kludge for Arasan 4.1 bug */
7421         if (strcmp(r, "Black resigns") == 0) {
7422             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7423             return;
7424         }
7425         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7426         return;
7427     } else if (strncmp(message, "1/2", 3) == 0) {
7428         char *p, *q, *r = "";
7429         p = strchr(message, '{');
7430         if (p) {
7431             q = strchr(p, '}');
7432             if (q) {
7433                 *q = NULLCHAR;
7434                 r = p + 1;
7435             }
7436         }
7437             
7438         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7439         return;
7440
7441     } else if (strncmp(message, "White resign", 12) == 0) {
7442         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7443         return;
7444     } else if (strncmp(message, "Black resign", 12) == 0) {
7445         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7446         return;
7447     } else if (strncmp(message, "White matches", 13) == 0 ||
7448                strncmp(message, "Black matches", 13) == 0   ) {
7449         /* [HGM] ignore GNUShogi noises */
7450         return;
7451     } else if (strncmp(message, "White", 5) == 0 &&
7452                message[5] != '(' &&
7453                StrStr(message, "Black") == NULL) {
7454         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7455         return;
7456     } else if (strncmp(message, "Black", 5) == 0 &&
7457                message[5] != '(') {
7458         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7459         return;
7460     } else if (strcmp(message, "resign") == 0 ||
7461                strcmp(message, "computer resigns") == 0) {
7462         switch (gameMode) {
7463           case MachinePlaysBlack:
7464           case IcsPlayingBlack:
7465             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7466             break;
7467           case MachinePlaysWhite:
7468           case IcsPlayingWhite:
7469             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7470             break;
7471           case TwoMachinesPlay:
7472             if (cps->twoMachinesColor[0] == 'w')
7473               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7474             else
7475               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7476             break;
7477           default:
7478             /* can't happen */
7479             break;
7480         }
7481         return;
7482     } else if (strncmp(message, "opponent mates", 14) == 0) {
7483         switch (gameMode) {
7484           case MachinePlaysBlack:
7485           case IcsPlayingBlack:
7486             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7487             break;
7488           case MachinePlaysWhite:
7489           case IcsPlayingWhite:
7490             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7491             break;
7492           case TwoMachinesPlay:
7493             if (cps->twoMachinesColor[0] == 'w')
7494               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7495             else
7496               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7497             break;
7498           default:
7499             /* can't happen */
7500             break;
7501         }
7502         return;
7503     } else if (strncmp(message, "computer mates", 14) == 0) {
7504         switch (gameMode) {
7505           case MachinePlaysBlack:
7506           case IcsPlayingBlack:
7507             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7508             break;
7509           case MachinePlaysWhite:
7510           case IcsPlayingWhite:
7511             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7512             break;
7513           case TwoMachinesPlay:
7514             if (cps->twoMachinesColor[0] == 'w')
7515               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7516             else
7517               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7518             break;
7519           default:
7520             /* can't happen */
7521             break;
7522         }
7523         return;
7524     } else if (strncmp(message, "checkmate", 9) == 0) {
7525         if (WhiteOnMove(forwardMostMove)) {
7526             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7527         } else {
7528             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7529         }
7530         return;
7531     } else if (strstr(message, "Draw") != NULL ||
7532                strstr(message, "game is a draw") != NULL) {
7533         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7534         return;
7535     } else if (strstr(message, "offer") != NULL &&
7536                strstr(message, "draw") != NULL) {
7537 #if ZIPPY
7538         if (appData.zippyPlay && first.initDone) {
7539             /* Relay offer to ICS */
7540             SendToICS(ics_prefix);
7541             SendToICS("draw\n");
7542         }
7543 #endif
7544         cps->offeredDraw = 2; /* valid until this engine moves twice */
7545         if (gameMode == TwoMachinesPlay) {
7546             if (cps->other->offeredDraw) {
7547                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7548             /* [HGM] in two-machine mode we delay relaying draw offer      */
7549             /* until after we also have move, to see if it is really claim */
7550             }
7551         } else if (gameMode == MachinePlaysWhite ||
7552                    gameMode == MachinePlaysBlack) {
7553           if (userOfferedDraw) {
7554             DisplayInformation(_("Machine accepts your draw offer"));
7555             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7556           } else {
7557             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7558           }
7559         }
7560     }
7561
7562     
7563     /*
7564      * Look for thinking output
7565      */
7566     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7567           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7568                                 ) {
7569         int plylev, mvleft, mvtot, curscore, time;
7570         char mvname[MOVE_LEN];
7571         u64 nodes; // [DM]
7572         char plyext;
7573         int ignore = FALSE;
7574         int prefixHint = FALSE;
7575         mvname[0] = NULLCHAR;
7576
7577         switch (gameMode) {
7578           case MachinePlaysBlack:
7579           case IcsPlayingBlack:
7580             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7581             break;
7582           case MachinePlaysWhite:
7583           case IcsPlayingWhite:
7584             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7585             break;
7586           case AnalyzeMode:
7587           case AnalyzeFile:
7588             break;
7589           case IcsObserving: /* [DM] icsEngineAnalyze */
7590             if (!appData.icsEngineAnalyze) ignore = TRUE;
7591             break;
7592           case TwoMachinesPlay:
7593             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7594                 ignore = TRUE;
7595             }
7596             break;
7597           default:
7598             ignore = TRUE;
7599             break;
7600         }
7601
7602         if (!ignore) {
7603             buf1[0] = NULLCHAR;
7604             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7605                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7606
7607                 if (plyext != ' ' && plyext != '\t') {
7608                     time *= 100;
7609                 }
7610
7611                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7612                 if( cps->scoreIsAbsolute && 
7613                     ( gameMode == MachinePlaysBlack ||
7614                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7615                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7616                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7617                      !WhiteOnMove(currentMove)
7618                     ) )
7619                 {
7620                     curscore = -curscore;
7621                 }
7622
7623
7624                 programStats.depth = plylev;
7625                 programStats.nodes = nodes;
7626                 programStats.time = time;
7627                 programStats.score = curscore;
7628                 programStats.got_only_move = 0;
7629
7630                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7631                         int ticklen;
7632
7633                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7634                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7635                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7636                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7637                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7638                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7639                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7640                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7641                 }
7642
7643                 /* Buffer overflow protection */
7644                 if (buf1[0] != NULLCHAR) {
7645                     if (strlen(buf1) >= sizeof(programStats.movelist)
7646                         && appData.debugMode) {
7647                         fprintf(debugFP,
7648                                 "PV is too long; using the first %u bytes.\n",
7649                                 (unsigned) sizeof(programStats.movelist) - 1);
7650                     }
7651
7652                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7653                 } else {
7654                     sprintf(programStats.movelist, " no PV\n");
7655                 }
7656
7657                 if (programStats.seen_stat) {
7658                     programStats.ok_to_send = 1;
7659                 }
7660
7661                 if (strchr(programStats.movelist, '(') != NULL) {
7662                     programStats.line_is_book = 1;
7663                     programStats.nr_moves = 0;
7664                     programStats.moves_left = 0;
7665                 } else {
7666                     programStats.line_is_book = 0;
7667                 }
7668
7669                 SendProgramStatsToFrontend( cps, &programStats );
7670
7671                 /* 
7672                     [AS] Protect the thinkOutput buffer from overflow... this
7673                     is only useful if buf1 hasn't overflowed first!
7674                 */
7675                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7676                         plylev, 
7677                         (gameMode == TwoMachinesPlay ?
7678                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7679                         ((double) curscore) / 100.0,
7680                         prefixHint ? lastHint : "",
7681                         prefixHint ? " " : "" );
7682
7683                 if( buf1[0] != NULLCHAR ) {
7684                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7685
7686                     if( strlen(buf1) > max_len ) {
7687                         if( appData.debugMode) {
7688                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7689                         }
7690                         buf1[max_len+1] = '\0';
7691                     }
7692
7693                     strcat( thinkOutput, buf1 );
7694                 }
7695
7696                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7697                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7698                     DisplayMove(currentMove - 1);
7699                 }
7700                 return;
7701
7702             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7703                 /* crafty (9.25+) says "(only move) <move>"
7704                  * if there is only 1 legal move
7705                  */
7706                 sscanf(p, "(only move) %s", buf1);
7707                 sprintf(thinkOutput, "%s (only move)", buf1);
7708                 sprintf(programStats.movelist, "%s (only move)", buf1);
7709                 programStats.depth = 1;
7710                 programStats.nr_moves = 1;
7711                 programStats.moves_left = 1;
7712                 programStats.nodes = 1;
7713                 programStats.time = 1;
7714                 programStats.got_only_move = 1;
7715
7716                 /* Not really, but we also use this member to
7717                    mean "line isn't going to change" (Crafty
7718                    isn't searching, so stats won't change) */
7719                 programStats.line_is_book = 1;
7720
7721                 SendProgramStatsToFrontend( cps, &programStats );
7722                 
7723                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7724                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7725                     DisplayMove(currentMove - 1);
7726                 }
7727                 return;
7728             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7729                               &time, &nodes, &plylev, &mvleft,
7730                               &mvtot, mvname) >= 5) {
7731                 /* The stat01: line is from Crafty (9.29+) in response
7732                    to the "." command */
7733                 programStats.seen_stat = 1;
7734                 cps->maybeThinking = TRUE;
7735
7736                 if (programStats.got_only_move || !appData.periodicUpdates)
7737                   return;
7738
7739                 programStats.depth = plylev;
7740                 programStats.time = time;
7741                 programStats.nodes = nodes;
7742                 programStats.moves_left = mvleft;
7743                 programStats.nr_moves = mvtot;
7744                 strcpy(programStats.move_name, mvname);
7745                 programStats.ok_to_send = 1;
7746                 programStats.movelist[0] = '\0';
7747
7748                 SendProgramStatsToFrontend( cps, &programStats );
7749
7750                 return;
7751
7752             } else if (strncmp(message,"++",2) == 0) {
7753                 /* Crafty 9.29+ outputs this */
7754                 programStats.got_fail = 2;
7755                 return;
7756
7757             } else if (strncmp(message,"--",2) == 0) {
7758                 /* Crafty 9.29+ outputs this */
7759                 programStats.got_fail = 1;
7760                 return;
7761
7762             } else if (thinkOutput[0] != NULLCHAR &&
7763                        strncmp(message, "    ", 4) == 0) {
7764                 unsigned message_len;
7765
7766                 p = message;
7767                 while (*p && *p == ' ') p++;
7768
7769                 message_len = strlen( p );
7770
7771                 /* [AS] Avoid buffer overflow */
7772                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7773                     strcat(thinkOutput, " ");
7774                     strcat(thinkOutput, p);
7775                 }
7776
7777                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7778                     strcat(programStats.movelist, " ");
7779                     strcat(programStats.movelist, p);
7780                 }
7781
7782                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7783                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7784                     DisplayMove(currentMove - 1);
7785                 }
7786                 return;
7787             }
7788         }
7789         else {
7790             buf1[0] = NULLCHAR;
7791
7792             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7793                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7794             {
7795                 ChessProgramStats cpstats;
7796
7797                 if (plyext != ' ' && plyext != '\t') {
7798                     time *= 100;
7799                 }
7800
7801                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7802                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7803                     curscore = -curscore;
7804                 }
7805
7806                 cpstats.depth = plylev;
7807                 cpstats.nodes = nodes;
7808                 cpstats.time = time;
7809                 cpstats.score = curscore;
7810                 cpstats.got_only_move = 0;
7811                 cpstats.movelist[0] = '\0';
7812
7813                 if (buf1[0] != NULLCHAR) {
7814                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7815                 }
7816
7817                 cpstats.ok_to_send = 0;
7818                 cpstats.line_is_book = 0;
7819                 cpstats.nr_moves = 0;
7820                 cpstats.moves_left = 0;
7821
7822                 SendProgramStatsToFrontend( cps, &cpstats );
7823             }
7824         }
7825     }
7826 }
7827
7828
7829 /* Parse a game score from the character string "game", and
7830    record it as the history of the current game.  The game
7831    score is NOT assumed to start from the standard position. 
7832    The display is not updated in any way.
7833    */
7834 void
7835 ParseGameHistory(game)
7836      char *game;
7837 {
7838     ChessMove moveType;
7839     int fromX, fromY, toX, toY, boardIndex;
7840     char promoChar;
7841     char *p, *q;
7842     char buf[MSG_SIZ];
7843
7844     if (appData.debugMode)
7845       fprintf(debugFP, "Parsing game history: %s\n", game);
7846
7847     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7848     gameInfo.site = StrSave(appData.icsHost);
7849     gameInfo.date = PGNDate();
7850     gameInfo.round = StrSave("-");
7851
7852     /* Parse out names of players */
7853     while (*game == ' ') game++;
7854     p = buf;
7855     while (*game != ' ') *p++ = *game++;
7856     *p = NULLCHAR;
7857     gameInfo.white = StrSave(buf);
7858     while (*game == ' ') game++;
7859     p = buf;
7860     while (*game != ' ' && *game != '\n') *p++ = *game++;
7861     *p = NULLCHAR;
7862     gameInfo.black = StrSave(buf);
7863
7864     /* Parse moves */
7865     boardIndex = blackPlaysFirst ? 1 : 0;
7866     yynewstr(game);
7867     for (;;) {
7868         yyboardindex = boardIndex;
7869         moveType = (ChessMove) yylex();
7870         switch (moveType) {
7871           case IllegalMove:             /* maybe suicide chess, etc. */
7872   if (appData.debugMode) {
7873     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7874     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7875     setbuf(debugFP, NULL);
7876   }
7877           case WhitePromotionChancellor:
7878           case BlackPromotionChancellor:
7879           case WhitePromotionArchbishop:
7880           case BlackPromotionArchbishop:
7881           case WhitePromotionQueen:
7882           case BlackPromotionQueen:
7883           case WhitePromotionRook:
7884           case BlackPromotionRook:
7885           case WhitePromotionBishop:
7886           case BlackPromotionBishop:
7887           case WhitePromotionKnight:
7888           case BlackPromotionKnight:
7889           case WhitePromotionKing:
7890           case BlackPromotionKing:
7891           case NormalMove:
7892           case WhiteCapturesEnPassant:
7893           case BlackCapturesEnPassant:
7894           case WhiteKingSideCastle:
7895           case WhiteQueenSideCastle:
7896           case BlackKingSideCastle:
7897           case BlackQueenSideCastle:
7898           case WhiteKingSideCastleWild:
7899           case WhiteQueenSideCastleWild:
7900           case BlackKingSideCastleWild:
7901           case BlackQueenSideCastleWild:
7902           /* PUSH Fabien */
7903           case WhiteHSideCastleFR:
7904           case WhiteASideCastleFR:
7905           case BlackHSideCastleFR:
7906           case BlackASideCastleFR:
7907           /* POP Fabien */
7908             fromX = currentMoveString[0] - AAA;
7909             fromY = currentMoveString[1] - ONE;
7910             toX = currentMoveString[2] - AAA;
7911             toY = currentMoveString[3] - ONE;
7912             promoChar = currentMoveString[4];
7913             break;
7914           case WhiteDrop:
7915           case BlackDrop:
7916             fromX = moveType == WhiteDrop ?
7917               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7918             (int) CharToPiece(ToLower(currentMoveString[0]));
7919             fromY = DROP_RANK;
7920             toX = currentMoveString[2] - AAA;
7921             toY = currentMoveString[3] - ONE;
7922             promoChar = NULLCHAR;
7923             break;
7924           case AmbiguousMove:
7925             /* bug? */
7926             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7927   if (appData.debugMode) {
7928     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7929     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7930     setbuf(debugFP, NULL);
7931   }
7932             DisplayError(buf, 0);
7933             return;
7934           case ImpossibleMove:
7935             /* bug? */
7936             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7937   if (appData.debugMode) {
7938     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7939     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7940     setbuf(debugFP, NULL);
7941   }
7942             DisplayError(buf, 0);
7943             return;
7944           case (ChessMove) 0:   /* end of file */
7945             if (boardIndex < backwardMostMove) {
7946                 /* Oops, gap.  How did that happen? */
7947                 DisplayError(_("Gap in move list"), 0);
7948                 return;
7949             }
7950             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7951             if (boardIndex > forwardMostMove) {
7952                 forwardMostMove = boardIndex;
7953             }
7954             return;
7955           case ElapsedTime:
7956             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7957                 strcat(parseList[boardIndex-1], " ");
7958                 strcat(parseList[boardIndex-1], yy_text);
7959             }
7960             continue;
7961           case Comment:
7962           case PGNTag:
7963           case NAG:
7964           default:
7965             /* ignore */
7966             continue;
7967           case WhiteWins:
7968           case BlackWins:
7969           case GameIsDrawn:
7970           case GameUnfinished:
7971             if (gameMode == IcsExamining) {
7972                 if (boardIndex < backwardMostMove) {
7973                     /* Oops, gap.  How did that happen? */
7974                     return;
7975                 }
7976                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7977                 return;
7978             }
7979             gameInfo.result = moveType;
7980             p = strchr(yy_text, '{');
7981             if (p == NULL) p = strchr(yy_text, '(');
7982             if (p == NULL) {
7983                 p = yy_text;
7984                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7985             } else {
7986                 q = strchr(p, *p == '{' ? '}' : ')');
7987                 if (q != NULL) *q = NULLCHAR;
7988                 p++;
7989             }
7990             gameInfo.resultDetails = StrSave(p);
7991             continue;
7992         }
7993         if (boardIndex >= forwardMostMove &&
7994             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7995             backwardMostMove = blackPlaysFirst ? 1 : 0;
7996             return;
7997         }
7998         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7999                                  fromY, fromX, toY, toX, promoChar,
8000                                  parseList[boardIndex]);
8001         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8002         /* currentMoveString is set as a side-effect of yylex */
8003         strcpy(moveList[boardIndex], currentMoveString);
8004         strcat(moveList[boardIndex], "\n");
8005         boardIndex++;
8006         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8007         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8008           case MT_NONE:
8009           case MT_STALEMATE:
8010           default:
8011             break;
8012           case MT_CHECK:
8013             if(gameInfo.variant != VariantShogi)
8014                 strcat(parseList[boardIndex - 1], "+");
8015             break;
8016           case MT_CHECKMATE:
8017           case MT_STAINMATE:
8018             strcat(parseList[boardIndex - 1], "#");
8019             break;
8020         }
8021     }
8022 }
8023
8024
8025 /* Apply a move to the given board  */
8026 void
8027 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8028      int fromX, fromY, toX, toY;
8029      int promoChar;
8030      Board board;
8031 {
8032   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8033   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8034
8035     /* [HGM] compute & store e.p. status and castling rights for new position */
8036     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8037     { int i;
8038
8039       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8040       oldEP = (signed char)board[EP_STATUS];
8041       board[EP_STATUS] = EP_NONE;
8042
8043       if( board[toY][toX] != EmptySquare ) 
8044            board[EP_STATUS] = EP_CAPTURE;  
8045
8046       if( board[fromY][fromX] == WhitePawn ) {
8047            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8048                board[EP_STATUS] = EP_PAWN_MOVE;
8049            if( toY-fromY==2) {
8050                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8051                         gameInfo.variant != VariantBerolina || toX < fromX)
8052                       board[EP_STATUS] = toX | berolina;
8053                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8054                         gameInfo.variant != VariantBerolina || toX > fromX) 
8055                       board[EP_STATUS] = toX;
8056            }
8057       } else 
8058       if( board[fromY][fromX] == BlackPawn ) {
8059            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8060                board[EP_STATUS] = EP_PAWN_MOVE; 
8061            if( toY-fromY== -2) {
8062                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8063                         gameInfo.variant != VariantBerolina || toX < fromX)
8064                       board[EP_STATUS] = toX | berolina;
8065                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8066                         gameInfo.variant != VariantBerolina || toX > fromX) 
8067                       board[EP_STATUS] = toX;
8068            }
8069        }
8070
8071        for(i=0; i<nrCastlingRights; i++) {
8072            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8073               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8074              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8075        }
8076
8077     }
8078
8079   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8080   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8081        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8082          
8083   if (fromX == toX && fromY == toY) return;
8084
8085   if (fromY == DROP_RANK) {
8086         /* must be first */
8087         piece = board[toY][toX] = (ChessSquare) fromX;
8088   } else {
8089      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8090      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8091      if(gameInfo.variant == VariantKnightmate)
8092          king += (int) WhiteUnicorn - (int) WhiteKing;
8093
8094     /* Code added by Tord: */
8095     /* FRC castling assumed when king captures friendly rook. */
8096     if (board[fromY][fromX] == WhiteKing &&
8097              board[toY][toX] == WhiteRook) {
8098       board[fromY][fromX] = EmptySquare;
8099       board[toY][toX] = EmptySquare;
8100       if(toX > fromX) {
8101         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8102       } else {
8103         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8104       }
8105     } else if (board[fromY][fromX] == BlackKing &&
8106                board[toY][toX] == BlackRook) {
8107       board[fromY][fromX] = EmptySquare;
8108       board[toY][toX] = EmptySquare;
8109       if(toX > fromX) {
8110         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8111       } else {
8112         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8113       }
8114     /* End of code added by Tord */
8115
8116     } else if (board[fromY][fromX] == king
8117         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8118         && toY == fromY && toX > fromX+1) {
8119         board[fromY][fromX] = EmptySquare;
8120         board[toY][toX] = king;
8121         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8122         board[fromY][BOARD_RGHT-1] = EmptySquare;
8123     } else if (board[fromY][fromX] == king
8124         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8125                && toY == fromY && toX < fromX-1) {
8126         board[fromY][fromX] = EmptySquare;
8127         board[toY][toX] = king;
8128         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8129         board[fromY][BOARD_LEFT] = EmptySquare;
8130     } else if (board[fromY][fromX] == WhitePawn
8131                && toY >= BOARD_HEIGHT-promoRank
8132                && gameInfo.variant != VariantXiangqi
8133                ) {
8134         /* white pawn promotion */
8135         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8136         if (board[toY][toX] == EmptySquare) {
8137             board[toY][toX] = WhiteQueen;
8138         }
8139         if(gameInfo.variant==VariantBughouse ||
8140            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8141             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8142         board[fromY][fromX] = EmptySquare;
8143     } else if ((fromY == BOARD_HEIGHT-4)
8144                && (toX != fromX)
8145                && gameInfo.variant != VariantXiangqi
8146                && gameInfo.variant != VariantBerolina
8147                && (board[fromY][fromX] == WhitePawn)
8148                && (board[toY][toX] == EmptySquare)) {
8149         board[fromY][fromX] = EmptySquare;
8150         board[toY][toX] = WhitePawn;
8151         captured = board[toY - 1][toX];
8152         board[toY - 1][toX] = EmptySquare;
8153     } else if ((fromY == BOARD_HEIGHT-4)
8154                && (toX == fromX)
8155                && gameInfo.variant == VariantBerolina
8156                && (board[fromY][fromX] == WhitePawn)
8157                && (board[toY][toX] == EmptySquare)) {
8158         board[fromY][fromX] = EmptySquare;
8159         board[toY][toX] = WhitePawn;
8160         if(oldEP & EP_BEROLIN_A) {
8161                 captured = board[fromY][fromX-1];
8162                 board[fromY][fromX-1] = EmptySquare;
8163         }else{  captured = board[fromY][fromX+1];
8164                 board[fromY][fromX+1] = EmptySquare;
8165         }
8166     } else if (board[fromY][fromX] == king
8167         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8168                && toY == fromY && toX > fromX+1) {
8169         board[fromY][fromX] = EmptySquare;
8170         board[toY][toX] = king;
8171         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8172         board[fromY][BOARD_RGHT-1] = EmptySquare;
8173     } else if (board[fromY][fromX] == king
8174         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8175                && toY == fromY && toX < fromX-1) {
8176         board[fromY][fromX] = EmptySquare;
8177         board[toY][toX] = king;
8178         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8179         board[fromY][BOARD_LEFT] = EmptySquare;
8180     } else if (fromY == 7 && fromX == 3
8181                && board[fromY][fromX] == BlackKing
8182                && toY == 7 && toX == 5) {
8183         board[fromY][fromX] = EmptySquare;
8184         board[toY][toX] = BlackKing;
8185         board[fromY][7] = EmptySquare;
8186         board[toY][4] = BlackRook;
8187     } else if (fromY == 7 && fromX == 3
8188                && board[fromY][fromX] == BlackKing
8189                && toY == 7 && toX == 1) {
8190         board[fromY][fromX] = EmptySquare;
8191         board[toY][toX] = BlackKing;
8192         board[fromY][0] = EmptySquare;
8193         board[toY][2] = BlackRook;
8194     } else if (board[fromY][fromX] == BlackPawn
8195                && toY < promoRank
8196                && gameInfo.variant != VariantXiangqi
8197                ) {
8198         /* black pawn promotion */
8199         board[toY][toX] = CharToPiece(ToLower(promoChar));
8200         if (board[toY][toX] == EmptySquare) {
8201             board[toY][toX] = BlackQueen;
8202         }
8203         if(gameInfo.variant==VariantBughouse ||
8204            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8205             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8206         board[fromY][fromX] = EmptySquare;
8207     } else if ((fromY == 3)
8208                && (toX != fromX)
8209                && gameInfo.variant != VariantXiangqi
8210                && gameInfo.variant != VariantBerolina
8211                && (board[fromY][fromX] == BlackPawn)
8212                && (board[toY][toX] == EmptySquare)) {
8213         board[fromY][fromX] = EmptySquare;
8214         board[toY][toX] = BlackPawn;
8215         captured = board[toY + 1][toX];
8216         board[toY + 1][toX] = EmptySquare;
8217     } else if ((fromY == 3)
8218                && (toX == fromX)
8219                && gameInfo.variant == VariantBerolina
8220                && (board[fromY][fromX] == BlackPawn)
8221                && (board[toY][toX] == EmptySquare)) {
8222         board[fromY][fromX] = EmptySquare;
8223         board[toY][toX] = BlackPawn;
8224         if(oldEP & EP_BEROLIN_A) {
8225                 captured = board[fromY][fromX-1];
8226                 board[fromY][fromX-1] = EmptySquare;
8227         }else{  captured = board[fromY][fromX+1];
8228                 board[fromY][fromX+1] = EmptySquare;
8229         }
8230     } else {
8231         board[toY][toX] = board[fromY][fromX];
8232         board[fromY][fromX] = EmptySquare;
8233     }
8234
8235     /* [HGM] now we promote for Shogi, if needed */
8236     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8237         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8238   }
8239
8240     if (gameInfo.holdingsWidth != 0) {
8241
8242       /* !!A lot more code needs to be written to support holdings  */
8243       /* [HGM] OK, so I have written it. Holdings are stored in the */
8244       /* penultimate board files, so they are automaticlly stored   */
8245       /* in the game history.                                       */
8246       if (fromY == DROP_RANK) {
8247         /* Delete from holdings, by decreasing count */
8248         /* and erasing image if necessary            */
8249         p = (int) fromX;
8250         if(p < (int) BlackPawn) { /* white drop */
8251              p -= (int)WhitePawn;
8252                  p = PieceToNumber((ChessSquare)p);
8253              if(p >= gameInfo.holdingsSize) p = 0;
8254              if(--board[p][BOARD_WIDTH-2] <= 0)
8255                   board[p][BOARD_WIDTH-1] = EmptySquare;
8256              if((int)board[p][BOARD_WIDTH-2] < 0)
8257                         board[p][BOARD_WIDTH-2] = 0;
8258         } else {                  /* black drop */
8259              p -= (int)BlackPawn;
8260                  p = PieceToNumber((ChessSquare)p);
8261              if(p >= gameInfo.holdingsSize) p = 0;
8262              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8263                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8264              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8265                         board[BOARD_HEIGHT-1-p][1] = 0;
8266         }
8267       }
8268       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8269           && gameInfo.variant != VariantBughouse        ) {
8270         /* [HGM] holdings: Add to holdings, if holdings exist */
8271         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8272                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8273                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8274         }
8275         p = (int) captured;
8276         if (p >= (int) BlackPawn) {
8277           p -= (int)BlackPawn;
8278           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8279                   /* in Shogi restore piece to its original  first */
8280                   captured = (ChessSquare) (DEMOTED captured);
8281                   p = DEMOTED p;
8282           }
8283           p = PieceToNumber((ChessSquare)p);
8284           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8285           board[p][BOARD_WIDTH-2]++;
8286           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8287         } else {
8288           p -= (int)WhitePawn;
8289           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8290                   captured = (ChessSquare) (DEMOTED captured);
8291                   p = DEMOTED p;
8292           }
8293           p = PieceToNumber((ChessSquare)p);
8294           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8295           board[BOARD_HEIGHT-1-p][1]++;
8296           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8297         }
8298       }
8299     } else if (gameInfo.variant == VariantAtomic) {
8300       if (captured != EmptySquare) {
8301         int y, x;
8302         for (y = toY-1; y <= toY+1; y++) {
8303           for (x = toX-1; x <= toX+1; x++) {
8304             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8305                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8306               board[y][x] = EmptySquare;
8307             }
8308           }
8309         }
8310         board[toY][toX] = EmptySquare;
8311       }
8312     }
8313     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8314         /* [HGM] Shogi promotions */
8315         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8316     }
8317
8318     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8319                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8320         // [HGM] superchess: take promotion piece out of holdings
8321         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8322         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8323             if(!--board[k][BOARD_WIDTH-2])
8324                 board[k][BOARD_WIDTH-1] = EmptySquare;
8325         } else {
8326             if(!--board[BOARD_HEIGHT-1-k][1])
8327                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8328         }
8329     }
8330
8331 }
8332
8333 /* Updates forwardMostMove */
8334 void
8335 MakeMove(fromX, fromY, toX, toY, promoChar)
8336      int fromX, fromY, toX, toY;
8337      int promoChar;
8338 {
8339 //    forwardMostMove++; // [HGM] bare: moved downstream
8340
8341     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8342         int timeLeft; static int lastLoadFlag=0; int king, piece;
8343         piece = boards[forwardMostMove][fromY][fromX];
8344         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8345         if(gameInfo.variant == VariantKnightmate)
8346             king += (int) WhiteUnicorn - (int) WhiteKing;
8347         if(forwardMostMove == 0) {
8348             if(blackPlaysFirst) 
8349                 fprintf(serverMoves, "%s;", second.tidy);
8350             fprintf(serverMoves, "%s;", first.tidy);
8351             if(!blackPlaysFirst) 
8352                 fprintf(serverMoves, "%s;", second.tidy);
8353         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8354         lastLoadFlag = loadFlag;
8355         // print base move
8356         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8357         // print castling suffix
8358         if( toY == fromY && piece == king ) {
8359             if(toX-fromX > 1)
8360                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8361             if(fromX-toX >1)
8362                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8363         }
8364         // e.p. suffix
8365         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8366              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8367              boards[forwardMostMove][toY][toX] == EmptySquare
8368              && fromX != toX )
8369                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8370         // promotion suffix
8371         if(promoChar != NULLCHAR)
8372                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8373         if(!loadFlag) {
8374             fprintf(serverMoves, "/%d/%d",
8375                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8376             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8377             else                      timeLeft = blackTimeRemaining/1000;
8378             fprintf(serverMoves, "/%d", timeLeft);
8379         }
8380         fflush(serverMoves);
8381     }
8382
8383     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8384       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8385                         0, 1);
8386       return;
8387     }
8388     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8389     if (commentList[forwardMostMove+1] != NULL) {
8390         free(commentList[forwardMostMove+1]);
8391         commentList[forwardMostMove+1] = NULL;
8392     }
8393     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8394     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8395     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8396     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8397     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8398     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8399     gameInfo.result = GameUnfinished;
8400     if (gameInfo.resultDetails != NULL) {
8401         free(gameInfo.resultDetails);
8402         gameInfo.resultDetails = NULL;
8403     }
8404     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8405                               moveList[forwardMostMove - 1]);
8406     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8407                              PosFlags(forwardMostMove - 1),
8408                              fromY, fromX, toY, toX, promoChar,
8409                              parseList[forwardMostMove - 1]);
8410     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8411       case MT_NONE:
8412       case MT_STALEMATE:
8413       default:
8414         break;
8415       case MT_CHECK:
8416         if(gameInfo.variant != VariantShogi)
8417             strcat(parseList[forwardMostMove - 1], "+");
8418         break;
8419       case MT_CHECKMATE:
8420       case MT_STAINMATE:
8421         strcat(parseList[forwardMostMove - 1], "#");
8422         break;
8423     }
8424     if (appData.debugMode) {
8425         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8426     }
8427
8428 }
8429
8430 /* Updates currentMove if not pausing */
8431 void
8432 ShowMove(fromX, fromY, toX, toY)
8433 {
8434     int instant = (gameMode == PlayFromGameFile) ?
8435         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8436     if(appData.noGUI) return;
8437     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8438         if (!instant) {
8439             if (forwardMostMove == currentMove + 1) {
8440                 AnimateMove(boards[forwardMostMove - 1],
8441                             fromX, fromY, toX, toY);
8442             }
8443             if (appData.highlightLastMove) {
8444                 SetHighlights(fromX, fromY, toX, toY);
8445             }
8446         }
8447         currentMove = forwardMostMove;
8448     }
8449
8450     if (instant) return;
8451
8452     DisplayMove(currentMove - 1);
8453     DrawPosition(FALSE, boards[currentMove]);
8454     DisplayBothClocks();
8455     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8456 }
8457
8458 void SendEgtPath(ChessProgramState *cps)
8459 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8460         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8461
8462         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8463
8464         while(*p) {
8465             char c, *q = name+1, *r, *s;
8466
8467             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8468             while(*p && *p != ',') *q++ = *p++;
8469             *q++ = ':'; *q = 0;
8470             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8471                 strcmp(name, ",nalimov:") == 0 ) {
8472                 // take nalimov path from the menu-changeable option first, if it is defined
8473                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8474                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8475             } else
8476             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8477                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8478                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8479                 s = r = StrStr(s, ":") + 1; // beginning of path info
8480                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8481                 c = *r; *r = 0;             // temporarily null-terminate path info
8482                     *--q = 0;               // strip of trailig ':' from name
8483                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8484                 *r = c;
8485                 SendToProgram(buf,cps);     // send egtbpath command for this format
8486             }
8487             if(*p == ',') p++; // read away comma to position for next format name
8488         }
8489 }
8490
8491 void
8492 InitChessProgram(cps, setup)
8493      ChessProgramState *cps;
8494      int setup; /* [HGM] needed to setup FRC opening position */
8495 {
8496     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8497     if (appData.noChessProgram) return;
8498     hintRequested = FALSE;
8499     bookRequested = FALSE;
8500
8501     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8502     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8503     if(cps->memSize) { /* [HGM] memory */
8504         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8505         SendToProgram(buf, cps);
8506     }
8507     SendEgtPath(cps); /* [HGM] EGT */
8508     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8509         sprintf(buf, "cores %d\n", appData.smpCores);
8510         SendToProgram(buf, cps);
8511     }
8512
8513     SendToProgram(cps->initString, cps);
8514     if (gameInfo.variant != VariantNormal &&
8515         gameInfo.variant != VariantLoadable
8516         /* [HGM] also send variant if board size non-standard */
8517         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8518                                             ) {
8519       char *v = VariantName(gameInfo.variant);
8520       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8521         /* [HGM] in protocol 1 we have to assume all variants valid */
8522         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8523         DisplayFatalError(buf, 0, 1);
8524         return;
8525       }
8526
8527       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8528       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8529       if( gameInfo.variant == VariantXiangqi )
8530            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8531       if( gameInfo.variant == VariantShogi )
8532            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8533       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8534            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8535       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8536                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8537            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8538       if( gameInfo.variant == VariantCourier )
8539            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8540       if( gameInfo.variant == VariantSuper )
8541            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8542       if( gameInfo.variant == VariantGreat )
8543            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8544
8545       if(overruled) {
8546            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8547                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8548            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8549            if(StrStr(cps->variants, b) == NULL) { 
8550                // specific sized variant not known, check if general sizing allowed
8551                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8552                    if(StrStr(cps->variants, "boardsize") == NULL) {
8553                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8554                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8555                        DisplayFatalError(buf, 0, 1);
8556                        return;
8557                    }
8558                    /* [HGM] here we really should compare with the maximum supported board size */
8559                }
8560            }
8561       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8562       sprintf(buf, "variant %s\n", b);
8563       SendToProgram(buf, cps);
8564     }
8565     currentlyInitializedVariant = gameInfo.variant;
8566
8567     /* [HGM] send opening position in FRC to first engine */
8568     if(setup) {
8569           SendToProgram("force\n", cps);
8570           SendBoard(cps, 0);
8571           /* engine is now in force mode! Set flag to wake it up after first move. */
8572           setboardSpoiledMachineBlack = 1;
8573     }
8574
8575     if (cps->sendICS) {
8576       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8577       SendToProgram(buf, cps);
8578     }
8579     cps->maybeThinking = FALSE;
8580     cps->offeredDraw = 0;
8581     if (!appData.icsActive) {
8582         SendTimeControl(cps, movesPerSession, timeControl,
8583                         timeIncrement, appData.searchDepth,
8584                         searchTime);
8585     }
8586     if (appData.showThinking 
8587         // [HGM] thinking: four options require thinking output to be sent
8588         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8589                                 ) {
8590         SendToProgram("post\n", cps);
8591     }
8592     SendToProgram("hard\n", cps);
8593     if (!appData.ponderNextMove) {
8594         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8595            it without being sure what state we are in first.  "hard"
8596            is not a toggle, so that one is OK.
8597          */
8598         SendToProgram("easy\n", cps);
8599     }
8600     if (cps->usePing) {
8601       sprintf(buf, "ping %d\n", ++cps->lastPing);
8602       SendToProgram(buf, cps);
8603     }
8604     cps->initDone = TRUE;
8605 }   
8606
8607
8608 void
8609 StartChessProgram(cps)
8610      ChessProgramState *cps;
8611 {
8612     char buf[MSG_SIZ];
8613     int err;
8614
8615     if (appData.noChessProgram) return;
8616     cps->initDone = FALSE;
8617
8618     if (strcmp(cps->host, "localhost") == 0) {
8619         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8620     } else if (*appData.remoteShell == NULLCHAR) {
8621         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8622     } else {
8623         if (*appData.remoteUser == NULLCHAR) {
8624           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8625                     cps->program);
8626         } else {
8627           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8628                     cps->host, appData.remoteUser, cps->program);
8629         }
8630         err = StartChildProcess(buf, "", &cps->pr);
8631     }
8632     
8633     if (err != 0) {
8634         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8635         DisplayFatalError(buf, err, 1);
8636         cps->pr = NoProc;
8637         cps->isr = NULL;
8638         return;
8639     }
8640     
8641     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8642     if (cps->protocolVersion > 1) {
8643       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8644       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8645       cps->comboCnt = 0;  //                and values of combo boxes
8646       SendToProgram(buf, cps);
8647     } else {
8648       SendToProgram("xboard\n", cps);
8649     }
8650 }
8651
8652
8653 void
8654 TwoMachinesEventIfReady P((void))
8655 {
8656   if (first.lastPing != first.lastPong) {
8657     DisplayMessage("", _("Waiting for first chess program"));
8658     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8659     return;
8660   }
8661   if (second.lastPing != second.lastPong) {
8662     DisplayMessage("", _("Waiting for second chess program"));
8663     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8664     return;
8665   }
8666   ThawUI();
8667   TwoMachinesEvent();
8668 }
8669
8670 void
8671 NextMatchGame P((void))
8672 {
8673     int index; /* [HGM] autoinc: step load index during match */
8674     Reset(FALSE, TRUE);
8675     if (*appData.loadGameFile != NULLCHAR) {
8676         index = appData.loadGameIndex;
8677         if(index < 0) { // [HGM] autoinc
8678             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8679             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8680         } 
8681         LoadGameFromFile(appData.loadGameFile,
8682                          index,
8683                          appData.loadGameFile, FALSE);
8684     } else if (*appData.loadPositionFile != NULLCHAR) {
8685         index = appData.loadPositionIndex;
8686         if(index < 0) { // [HGM] autoinc
8687             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8688             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8689         } 
8690         LoadPositionFromFile(appData.loadPositionFile,
8691                              index,
8692                              appData.loadPositionFile);
8693     }
8694     TwoMachinesEventIfReady();
8695 }
8696
8697 void UserAdjudicationEvent( int result )
8698 {
8699     ChessMove gameResult = GameIsDrawn;
8700
8701     if( result > 0 ) {
8702         gameResult = WhiteWins;
8703     }
8704     else if( result < 0 ) {
8705         gameResult = BlackWins;
8706     }
8707
8708     if( gameMode == TwoMachinesPlay ) {
8709         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8710     }
8711 }
8712
8713
8714 // [HGM] save: calculate checksum of game to make games easily identifiable
8715 int StringCheckSum(char *s)
8716 {
8717         int i = 0;
8718         if(s==NULL) return 0;
8719         while(*s) i = i*259 + *s++;
8720         return i;
8721 }
8722
8723 int GameCheckSum()
8724 {
8725         int i, sum=0;
8726         for(i=backwardMostMove; i<forwardMostMove; i++) {
8727                 sum += pvInfoList[i].depth;
8728                 sum += StringCheckSum(parseList[i]);
8729                 sum += StringCheckSum(commentList[i]);
8730                 sum *= 261;
8731         }
8732         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8733         return sum + StringCheckSum(commentList[i]);
8734 } // end of save patch
8735
8736 void
8737 GameEnds(result, resultDetails, whosays)
8738      ChessMove result;
8739      char *resultDetails;
8740      int whosays;
8741 {
8742     GameMode nextGameMode;
8743     int isIcsGame;
8744     char buf[MSG_SIZ];
8745
8746     if(endingGame) return; /* [HGM] crash: forbid recursion */
8747     endingGame = 1;
8748
8749     if (appData.debugMode) {
8750       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8751               result, resultDetails ? resultDetails : "(null)", whosays);
8752     }
8753
8754     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8755
8756     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8757         /* If we are playing on ICS, the server decides when the
8758            game is over, but the engine can offer to draw, claim 
8759            a draw, or resign. 
8760          */
8761 #if ZIPPY
8762         if (appData.zippyPlay && first.initDone) {
8763             if (result == GameIsDrawn) {
8764                 /* In case draw still needs to be claimed */
8765                 SendToICS(ics_prefix);
8766                 SendToICS("draw\n");
8767             } else if (StrCaseStr(resultDetails, "resign")) {
8768                 SendToICS(ics_prefix);
8769                 SendToICS("resign\n");
8770             }
8771         }
8772 #endif
8773         endingGame = 0; /* [HGM] crash */
8774         return;
8775     }
8776
8777     /* If we're loading the game from a file, stop */
8778     if (whosays == GE_FILE) {
8779       (void) StopLoadGameTimer();
8780       gameFileFP = NULL;
8781     }
8782
8783     /* Cancel draw offers */
8784     first.offeredDraw = second.offeredDraw = 0;
8785
8786     /* If this is an ICS game, only ICS can really say it's done;
8787        if not, anyone can. */
8788     isIcsGame = (gameMode == IcsPlayingWhite || 
8789                  gameMode == IcsPlayingBlack || 
8790                  gameMode == IcsObserving    || 
8791                  gameMode == IcsExamining);
8792
8793     if (!isIcsGame || whosays == GE_ICS) {
8794         /* OK -- not an ICS game, or ICS said it was done */
8795         StopClocks();
8796         if (!isIcsGame && !appData.noChessProgram) 
8797           SetUserThinkingEnables();
8798     
8799         /* [HGM] if a machine claims the game end we verify this claim */
8800         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8801             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8802                 char claimer;
8803                 ChessMove trueResult = (ChessMove) -1;
8804
8805                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8806                                             first.twoMachinesColor[0] :
8807                                             second.twoMachinesColor[0] ;
8808
8809                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8810                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8811                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8812                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8813                 } else
8814                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8815                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8816                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8817                 } else
8818                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8819                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8820                 }
8821
8822                 // now verify win claims, but not in drop games, as we don't understand those yet
8823                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8824                                                  || gameInfo.variant == VariantGreat) &&
8825                     (result == WhiteWins && claimer == 'w' ||
8826                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8827                       if (appData.debugMode) {
8828                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8829                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8830                       }
8831                       if(result != trueResult) {
8832                               sprintf(buf, "False win claim: '%s'", resultDetails);
8833                               result = claimer == 'w' ? BlackWins : WhiteWins;
8834                               resultDetails = buf;
8835                       }
8836                 } else
8837                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8838                     && (forwardMostMove <= backwardMostMove ||
8839                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8840                         (claimer=='b')==(forwardMostMove&1))
8841                                                                                   ) {
8842                       /* [HGM] verify: draws that were not flagged are false claims */
8843                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8844                       result = claimer == 'w' ? BlackWins : WhiteWins;
8845                       resultDetails = buf;
8846                 }
8847                 /* (Claiming a loss is accepted no questions asked!) */
8848             }
8849             /* [HGM] bare: don't allow bare King to win */
8850             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8851                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8852                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8853                && result != GameIsDrawn)
8854             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8855                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8856                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8857                         if(p >= 0 && p <= (int)WhiteKing) k++;
8858                 }
8859                 if (appData.debugMode) {
8860                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8861                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8862                 }
8863                 if(k <= 1) {
8864                         result = GameIsDrawn;
8865                         sprintf(buf, "%s but bare king", resultDetails);
8866                         resultDetails = buf;
8867                 }
8868             }
8869         }
8870
8871
8872         if(serverMoves != NULL && !loadFlag) { char c = '=';
8873             if(result==WhiteWins) c = '+';
8874             if(result==BlackWins) c = '-';
8875             if(resultDetails != NULL)
8876                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8877         }
8878         if (resultDetails != NULL) {
8879             gameInfo.result = result;
8880             gameInfo.resultDetails = StrSave(resultDetails);
8881
8882             /* display last move only if game was not loaded from file */
8883             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8884                 DisplayMove(currentMove - 1);
8885     
8886             if (forwardMostMove != 0) {
8887                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8888                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8889                                                                 ) {
8890                     if (*appData.saveGameFile != NULLCHAR) {
8891                         SaveGameToFile(appData.saveGameFile, TRUE);
8892                     } else if (appData.autoSaveGames) {
8893                         AutoSaveGame();
8894                     }
8895                     if (*appData.savePositionFile != NULLCHAR) {
8896                         SavePositionToFile(appData.savePositionFile);
8897                     }
8898                 }
8899             }
8900
8901             /* Tell program how game ended in case it is learning */
8902             /* [HGM] Moved this to after saving the PGN, just in case */
8903             /* engine died and we got here through time loss. In that */
8904             /* case we will get a fatal error writing the pipe, which */
8905             /* would otherwise lose us the PGN.                       */
8906             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8907             /* output during GameEnds should never be fatal anymore   */
8908             if (gameMode == MachinePlaysWhite ||
8909                 gameMode == MachinePlaysBlack ||
8910                 gameMode == TwoMachinesPlay ||
8911                 gameMode == IcsPlayingWhite ||
8912                 gameMode == IcsPlayingBlack ||
8913                 gameMode == BeginningOfGame) {
8914                 char buf[MSG_SIZ];
8915                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8916                         resultDetails);
8917                 if (first.pr != NoProc) {
8918                     SendToProgram(buf, &first);
8919                 }
8920                 if (second.pr != NoProc &&
8921                     gameMode == TwoMachinesPlay) {
8922                     SendToProgram(buf, &second);
8923                 }
8924             }
8925         }
8926
8927         if (appData.icsActive) {
8928             if (appData.quietPlay &&
8929                 (gameMode == IcsPlayingWhite ||
8930                  gameMode == IcsPlayingBlack)) {
8931                 SendToICS(ics_prefix);
8932                 SendToICS("set shout 1\n");
8933             }
8934             nextGameMode = IcsIdle;
8935             ics_user_moved = FALSE;
8936             /* clean up premove.  It's ugly when the game has ended and the
8937              * premove highlights are still on the board.
8938              */
8939             if (gotPremove) {
8940               gotPremove = FALSE;
8941               ClearPremoveHighlights();
8942               DrawPosition(FALSE, boards[currentMove]);
8943             }
8944             if (whosays == GE_ICS) {
8945                 switch (result) {
8946                 case WhiteWins:
8947                     if (gameMode == IcsPlayingWhite)
8948                         PlayIcsWinSound();
8949                     else if(gameMode == IcsPlayingBlack)
8950                         PlayIcsLossSound();
8951                     break;
8952                 case BlackWins:
8953                     if (gameMode == IcsPlayingBlack)
8954                         PlayIcsWinSound();
8955                     else if(gameMode == IcsPlayingWhite)
8956                         PlayIcsLossSound();
8957                     break;
8958                 case GameIsDrawn:
8959                     PlayIcsDrawSound();
8960                     break;
8961                 default:
8962                     PlayIcsUnfinishedSound();
8963                 }
8964             }
8965         } else if (gameMode == EditGame ||
8966                    gameMode == PlayFromGameFile || 
8967                    gameMode == AnalyzeMode || 
8968                    gameMode == AnalyzeFile) {
8969             nextGameMode = gameMode;
8970         } else {
8971             nextGameMode = EndOfGame;
8972         }
8973         pausing = FALSE;
8974         ModeHighlight();
8975     } else {
8976         nextGameMode = gameMode;
8977     }
8978
8979     if (appData.noChessProgram) {
8980         gameMode = nextGameMode;
8981         ModeHighlight();
8982         endingGame = 0; /* [HGM] crash */
8983         return;
8984     }
8985
8986     if (first.reuse) {
8987         /* Put first chess program into idle state */
8988         if (first.pr != NoProc &&
8989             (gameMode == MachinePlaysWhite ||
8990              gameMode == MachinePlaysBlack ||
8991              gameMode == TwoMachinesPlay ||
8992              gameMode == IcsPlayingWhite ||
8993              gameMode == IcsPlayingBlack ||
8994              gameMode == BeginningOfGame)) {
8995             SendToProgram("force\n", &first);
8996             if (first.usePing) {
8997               char buf[MSG_SIZ];
8998               sprintf(buf, "ping %d\n", ++first.lastPing);
8999               SendToProgram(buf, &first);
9000             }
9001         }
9002     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9003         /* Kill off first chess program */
9004         if (first.isr != NULL)
9005           RemoveInputSource(first.isr);
9006         first.isr = NULL;
9007     
9008         if (first.pr != NoProc) {
9009             ExitAnalyzeMode();
9010             DoSleep( appData.delayBeforeQuit );
9011             SendToProgram("quit\n", &first);
9012             DoSleep( appData.delayAfterQuit );
9013             DestroyChildProcess(first.pr, first.useSigterm);
9014         }
9015         first.pr = NoProc;
9016     }
9017     if (second.reuse) {
9018         /* Put second chess program into idle state */
9019         if (second.pr != NoProc &&
9020             gameMode == TwoMachinesPlay) {
9021             SendToProgram("force\n", &second);
9022             if (second.usePing) {
9023               char buf[MSG_SIZ];
9024               sprintf(buf, "ping %d\n", ++second.lastPing);
9025               SendToProgram(buf, &second);
9026             }
9027         }
9028     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9029         /* Kill off second chess program */
9030         if (second.isr != NULL)
9031           RemoveInputSource(second.isr);
9032         second.isr = NULL;
9033     
9034         if (second.pr != NoProc) {
9035             DoSleep( appData.delayBeforeQuit );
9036             SendToProgram("quit\n", &second);
9037             DoSleep( appData.delayAfterQuit );
9038             DestroyChildProcess(second.pr, second.useSigterm);
9039         }
9040         second.pr = NoProc;
9041     }
9042
9043     if (matchMode && gameMode == TwoMachinesPlay) {
9044         switch (result) {
9045         case WhiteWins:
9046           if (first.twoMachinesColor[0] == 'w') {
9047             first.matchWins++;
9048           } else {
9049             second.matchWins++;
9050           }
9051           break;
9052         case BlackWins:
9053           if (first.twoMachinesColor[0] == 'b') {
9054             first.matchWins++;
9055           } else {
9056             second.matchWins++;
9057           }
9058           break;
9059         default:
9060           break;
9061         }
9062         if (matchGame < appData.matchGames) {
9063             char *tmp;
9064             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9065                 tmp = first.twoMachinesColor;
9066                 first.twoMachinesColor = second.twoMachinesColor;
9067                 second.twoMachinesColor = tmp;
9068             }
9069             gameMode = nextGameMode;
9070             matchGame++;
9071             if(appData.matchPause>10000 || appData.matchPause<10)
9072                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9073             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9074             endingGame = 0; /* [HGM] crash */
9075             return;
9076         } else {
9077             char buf[MSG_SIZ];
9078             gameMode = nextGameMode;
9079             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9080                     first.tidy, second.tidy,
9081                     first.matchWins, second.matchWins,
9082                     appData.matchGames - (first.matchWins + second.matchWins));
9083             DisplayFatalError(buf, 0, 0);
9084         }
9085     }
9086     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9087         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9088       ExitAnalyzeMode();
9089     gameMode = nextGameMode;
9090     ModeHighlight();
9091     endingGame = 0;  /* [HGM] crash */
9092 }
9093
9094 /* Assumes program was just initialized (initString sent).
9095    Leaves program in force mode. */
9096 void
9097 FeedMovesToProgram(cps, upto) 
9098      ChessProgramState *cps;
9099      int upto;
9100 {
9101     int i;
9102     
9103     if (appData.debugMode)
9104       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9105               startedFromSetupPosition ? "position and " : "",
9106               backwardMostMove, upto, cps->which);
9107     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9108         // [HGM] variantswitch: make engine aware of new variant
9109         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9110                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9111         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9112         SendToProgram(buf, cps);
9113         currentlyInitializedVariant = gameInfo.variant;
9114     }
9115     SendToProgram("force\n", cps);
9116     if (startedFromSetupPosition) {
9117         SendBoard(cps, backwardMostMove);
9118     if (appData.debugMode) {
9119         fprintf(debugFP, "feedMoves\n");
9120     }
9121     }
9122     for (i = backwardMostMove; i < upto; i++) {
9123         SendMoveToProgram(i, cps);
9124     }
9125 }
9126
9127
9128 void
9129 ResurrectChessProgram()
9130 {
9131      /* The chess program may have exited.
9132         If so, restart it and feed it all the moves made so far. */
9133
9134     if (appData.noChessProgram || first.pr != NoProc) return;
9135     
9136     StartChessProgram(&first);
9137     InitChessProgram(&first, FALSE);
9138     FeedMovesToProgram(&first, currentMove);
9139
9140     if (!first.sendTime) {
9141         /* can't tell gnuchess what its clock should read,
9142            so we bow to its notion. */
9143         ResetClocks();
9144         timeRemaining[0][currentMove] = whiteTimeRemaining;
9145         timeRemaining[1][currentMove] = blackTimeRemaining;
9146     }
9147
9148     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9149                 appData.icsEngineAnalyze) && first.analysisSupport) {
9150       SendToProgram("analyze\n", &first);
9151       first.analyzing = TRUE;
9152     }
9153 }
9154
9155 /*
9156  * Button procedures
9157  */
9158 void
9159 Reset(redraw, init)
9160      int redraw, init;
9161 {
9162     int i;
9163
9164     if (appData.debugMode) {
9165         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9166                 redraw, init, gameMode);
9167     }
9168     CleanupTail(); // [HGM] vari: delete any stored variations
9169     pausing = pauseExamInvalid = FALSE;
9170     startedFromSetupPosition = blackPlaysFirst = FALSE;
9171     firstMove = TRUE;
9172     whiteFlag = blackFlag = FALSE;
9173     userOfferedDraw = FALSE;
9174     hintRequested = bookRequested = FALSE;
9175     first.maybeThinking = FALSE;
9176     second.maybeThinking = FALSE;
9177     first.bookSuspend = FALSE; // [HGM] book
9178     second.bookSuspend = FALSE;
9179     thinkOutput[0] = NULLCHAR;
9180     lastHint[0] = NULLCHAR;
9181     ClearGameInfo(&gameInfo);
9182     gameInfo.variant = StringToVariant(appData.variant);
9183     ics_user_moved = ics_clock_paused = FALSE;
9184     ics_getting_history = H_FALSE;
9185     ics_gamenum = -1;
9186     white_holding[0] = black_holding[0] = NULLCHAR;
9187     ClearProgramStats();
9188     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9189     
9190     ResetFrontEnd();
9191     ClearHighlights();
9192     flipView = appData.flipView;
9193     ClearPremoveHighlights();
9194     gotPremove = FALSE;
9195     alarmSounded = FALSE;
9196
9197     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9198     if(appData.serverMovesName != NULL) {
9199         /* [HGM] prepare to make moves file for broadcasting */
9200         clock_t t = clock();
9201         if(serverMoves != NULL) fclose(serverMoves);
9202         serverMoves = fopen(appData.serverMovesName, "r");
9203         if(serverMoves != NULL) {
9204             fclose(serverMoves);
9205             /* delay 15 sec before overwriting, so all clients can see end */
9206             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9207         }
9208         serverMoves = fopen(appData.serverMovesName, "w");
9209     }
9210
9211     ExitAnalyzeMode();
9212     gameMode = BeginningOfGame;
9213     ModeHighlight();
9214     if(appData.icsActive) gameInfo.variant = VariantNormal;
9215     currentMove = forwardMostMove = backwardMostMove = 0;
9216     InitPosition(redraw);
9217     for (i = 0; i < MAX_MOVES; i++) {
9218         if (commentList[i] != NULL) {
9219             free(commentList[i]);
9220             commentList[i] = NULL;
9221         }
9222     }
9223     ResetClocks();
9224     timeRemaining[0][0] = whiteTimeRemaining;
9225     timeRemaining[1][0] = blackTimeRemaining;
9226     if (first.pr == NULL) {
9227         StartChessProgram(&first);
9228     }
9229     if (init) {
9230             InitChessProgram(&first, startedFromSetupPosition);
9231     }
9232     DisplayTitle("");
9233     DisplayMessage("", "");
9234     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9235     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9236 }
9237
9238 void
9239 AutoPlayGameLoop()
9240 {
9241     for (;;) {
9242         if (!AutoPlayOneMove())
9243           return;
9244         if (matchMode || appData.timeDelay == 0)
9245           continue;
9246         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9247           return;
9248         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9249         break;
9250     }
9251 }
9252
9253
9254 int
9255 AutoPlayOneMove()
9256 {
9257     int fromX, fromY, toX, toY;
9258
9259     if (appData.debugMode) {
9260       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9261     }
9262
9263     if (gameMode != PlayFromGameFile)
9264       return FALSE;
9265
9266     if (currentMove >= forwardMostMove) {
9267       gameMode = EditGame;
9268       ModeHighlight();
9269
9270       /* [AS] Clear current move marker at the end of a game */
9271       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9272
9273       return FALSE;
9274     }
9275     
9276     toX = moveList[currentMove][2] - AAA;
9277     toY = moveList[currentMove][3] - ONE;
9278
9279     if (moveList[currentMove][1] == '@') {
9280         if (appData.highlightLastMove) {
9281             SetHighlights(-1, -1, toX, toY);
9282         }
9283     } else {
9284         fromX = moveList[currentMove][0] - AAA;
9285         fromY = moveList[currentMove][1] - ONE;
9286
9287         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9288
9289         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9290
9291         if (appData.highlightLastMove) {
9292             SetHighlights(fromX, fromY, toX, toY);
9293         }
9294     }
9295     DisplayMove(currentMove);
9296     SendMoveToProgram(currentMove++, &first);
9297     DisplayBothClocks();
9298     DrawPosition(FALSE, boards[currentMove]);
9299     // [HGM] PV info: always display, routine tests if empty
9300     DisplayComment(currentMove - 1, commentList[currentMove]);
9301     return TRUE;
9302 }
9303
9304
9305 int
9306 LoadGameOneMove(readAhead)
9307      ChessMove readAhead;
9308 {
9309     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9310     char promoChar = NULLCHAR;
9311     ChessMove moveType;
9312     char move[MSG_SIZ];
9313     char *p, *q;
9314     
9315     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9316         gameMode != AnalyzeMode && gameMode != Training) {
9317         gameFileFP = NULL;
9318         return FALSE;
9319     }
9320     
9321     yyboardindex = forwardMostMove;
9322     if (readAhead != (ChessMove)0) {
9323       moveType = readAhead;
9324     } else {
9325       if (gameFileFP == NULL)
9326           return FALSE;
9327       moveType = (ChessMove) yylex();
9328     }
9329     
9330     done = FALSE;
9331     switch (moveType) {
9332       case Comment:
9333         if (appData.debugMode) 
9334           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9335         p = yy_text;
9336
9337         /* append the comment but don't display it */
9338         AppendComment(currentMove, p, FALSE);
9339         return TRUE;
9340
9341       case WhiteCapturesEnPassant:
9342       case BlackCapturesEnPassant:
9343       case WhitePromotionChancellor:
9344       case BlackPromotionChancellor:
9345       case WhitePromotionArchbishop:
9346       case BlackPromotionArchbishop:
9347       case WhitePromotionCentaur:
9348       case BlackPromotionCentaur:
9349       case WhitePromotionQueen:
9350       case BlackPromotionQueen:
9351       case WhitePromotionRook:
9352       case BlackPromotionRook:
9353       case WhitePromotionBishop:
9354       case BlackPromotionBishop:
9355       case WhitePromotionKnight:
9356       case BlackPromotionKnight:
9357       case WhitePromotionKing:
9358       case BlackPromotionKing:
9359       case NormalMove:
9360       case WhiteKingSideCastle:
9361       case WhiteQueenSideCastle:
9362       case BlackKingSideCastle:
9363       case BlackQueenSideCastle:
9364       case WhiteKingSideCastleWild:
9365       case WhiteQueenSideCastleWild:
9366       case BlackKingSideCastleWild:
9367       case BlackQueenSideCastleWild:
9368       /* PUSH Fabien */
9369       case WhiteHSideCastleFR:
9370       case WhiteASideCastleFR:
9371       case BlackHSideCastleFR:
9372       case BlackASideCastleFR:
9373       /* POP Fabien */
9374         if (appData.debugMode)
9375           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9376         fromX = currentMoveString[0] - AAA;
9377         fromY = currentMoveString[1] - ONE;
9378         toX = currentMoveString[2] - AAA;
9379         toY = currentMoveString[3] - ONE;
9380         promoChar = currentMoveString[4];
9381         break;
9382
9383       case WhiteDrop:
9384       case BlackDrop:
9385         if (appData.debugMode)
9386           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9387         fromX = moveType == WhiteDrop ?
9388           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9389         (int) CharToPiece(ToLower(currentMoveString[0]));
9390         fromY = DROP_RANK;
9391         toX = currentMoveString[2] - AAA;
9392         toY = currentMoveString[3] - ONE;
9393         break;
9394
9395       case WhiteWins:
9396       case BlackWins:
9397       case GameIsDrawn:
9398       case GameUnfinished:
9399         if (appData.debugMode)
9400           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9401         p = strchr(yy_text, '{');
9402         if (p == NULL) p = strchr(yy_text, '(');
9403         if (p == NULL) {
9404             p = yy_text;
9405             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9406         } else {
9407             q = strchr(p, *p == '{' ? '}' : ')');
9408             if (q != NULL) *q = NULLCHAR;
9409             p++;
9410         }
9411         GameEnds(moveType, p, GE_FILE);
9412         done = TRUE;
9413         if (cmailMsgLoaded) {
9414             ClearHighlights();
9415             flipView = WhiteOnMove(currentMove);
9416             if (moveType == GameUnfinished) flipView = !flipView;
9417             if (appData.debugMode)
9418               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9419         }
9420         break;
9421
9422       case (ChessMove) 0:       /* end of file */
9423         if (appData.debugMode)
9424           fprintf(debugFP, "Parser hit end of file\n");
9425         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9426           case MT_NONE:
9427           case MT_CHECK:
9428             break;
9429           case MT_CHECKMATE:
9430           case MT_STAINMATE:
9431             if (WhiteOnMove(currentMove)) {
9432                 GameEnds(BlackWins, "Black mates", GE_FILE);
9433             } else {
9434                 GameEnds(WhiteWins, "White mates", GE_FILE);
9435             }
9436             break;
9437           case MT_STALEMATE:
9438             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9439             break;
9440         }
9441         done = TRUE;
9442         break;
9443
9444       case MoveNumberOne:
9445         if (lastLoadGameStart == GNUChessGame) {
9446             /* GNUChessGames have numbers, but they aren't move numbers */
9447             if (appData.debugMode)
9448               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9449                       yy_text, (int) moveType);
9450             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9451         }
9452         /* else fall thru */
9453
9454       case XBoardGame:
9455       case GNUChessGame:
9456       case PGNTag:
9457         /* Reached start of next game in file */
9458         if (appData.debugMode)
9459           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9460         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9461           case MT_NONE:
9462           case MT_CHECK:
9463             break;
9464           case MT_CHECKMATE:
9465           case MT_STAINMATE:
9466             if (WhiteOnMove(currentMove)) {
9467                 GameEnds(BlackWins, "Black mates", GE_FILE);
9468             } else {
9469                 GameEnds(WhiteWins, "White mates", GE_FILE);
9470             }
9471             break;
9472           case MT_STALEMATE:
9473             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9474             break;
9475         }
9476         done = TRUE;
9477         break;
9478
9479       case PositionDiagram:     /* should not happen; ignore */
9480       case ElapsedTime:         /* ignore */
9481       case NAG:                 /* ignore */
9482         if (appData.debugMode)
9483           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9484                   yy_text, (int) moveType);
9485         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9486
9487       case IllegalMove:
9488         if (appData.testLegality) {
9489             if (appData.debugMode)
9490               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9491             sprintf(move, _("Illegal move: %d.%s%s"),
9492                     (forwardMostMove / 2) + 1,
9493                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9494             DisplayError(move, 0);
9495             done = TRUE;
9496         } else {
9497             if (appData.debugMode)
9498               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9499                       yy_text, currentMoveString);
9500             fromX = currentMoveString[0] - AAA;
9501             fromY = currentMoveString[1] - ONE;
9502             toX = currentMoveString[2] - AAA;
9503             toY = currentMoveString[3] - ONE;
9504             promoChar = currentMoveString[4];
9505         }
9506         break;
9507
9508       case AmbiguousMove:
9509         if (appData.debugMode)
9510           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9511         sprintf(move, _("Ambiguous move: %d.%s%s"),
9512                 (forwardMostMove / 2) + 1,
9513                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9514         DisplayError(move, 0);
9515         done = TRUE;
9516         break;
9517
9518       default:
9519       case ImpossibleMove:
9520         if (appData.debugMode)
9521           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9522         sprintf(move, _("Illegal move: %d.%s%s"),
9523                 (forwardMostMove / 2) + 1,
9524                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9525         DisplayError(move, 0);
9526         done = TRUE;
9527         break;
9528     }
9529
9530     if (done) {
9531         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9532             DrawPosition(FALSE, boards[currentMove]);
9533             DisplayBothClocks();
9534             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9535               DisplayComment(currentMove - 1, commentList[currentMove]);
9536         }
9537         (void) StopLoadGameTimer();
9538         gameFileFP = NULL;
9539         cmailOldMove = forwardMostMove;
9540         return FALSE;
9541     } else {
9542         /* currentMoveString is set as a side-effect of yylex */
9543         strcat(currentMoveString, "\n");
9544         strcpy(moveList[forwardMostMove], currentMoveString);
9545         
9546         thinkOutput[0] = NULLCHAR;
9547         MakeMove(fromX, fromY, toX, toY, promoChar);
9548         currentMove = forwardMostMove;
9549         return TRUE;
9550     }
9551 }
9552
9553 /* Load the nth game from the given file */
9554 int
9555 LoadGameFromFile(filename, n, title, useList)
9556      char *filename;
9557      int n;
9558      char *title;
9559      /*Boolean*/ int useList;
9560 {
9561     FILE *f;
9562     char buf[MSG_SIZ];
9563
9564     if (strcmp(filename, "-") == 0) {
9565         f = stdin;
9566         title = "stdin";
9567     } else {
9568         f = fopen(filename, "rb");
9569         if (f == NULL) {
9570           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9571             DisplayError(buf, errno);
9572             return FALSE;
9573         }
9574     }
9575     if (fseek(f, 0, 0) == -1) {
9576         /* f is not seekable; probably a pipe */
9577         useList = FALSE;
9578     }
9579     if (useList && n == 0) {
9580         int error = GameListBuild(f);
9581         if (error) {
9582             DisplayError(_("Cannot build game list"), error);
9583         } else if (!ListEmpty(&gameList) &&
9584                    ((ListGame *) gameList.tailPred)->number > 1) {
9585             GameListPopUp(f, title);
9586             return TRUE;
9587         }
9588         GameListDestroy();
9589         n = 1;
9590     }
9591     if (n == 0) n = 1;
9592     return LoadGame(f, n, title, FALSE);
9593 }
9594
9595
9596 void
9597 MakeRegisteredMove()
9598 {
9599     int fromX, fromY, toX, toY;
9600     char promoChar;
9601     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9602         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9603           case CMAIL_MOVE:
9604           case CMAIL_DRAW:
9605             if (appData.debugMode)
9606               fprintf(debugFP, "Restoring %s for game %d\n",
9607                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9608     
9609             thinkOutput[0] = NULLCHAR;
9610             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9611             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9612             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9613             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9614             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9615             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9616             MakeMove(fromX, fromY, toX, toY, promoChar);
9617             ShowMove(fromX, fromY, toX, toY);
9618               
9619             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9620               case MT_NONE:
9621               case MT_CHECK:
9622                 break;
9623                 
9624               case MT_CHECKMATE:
9625               case MT_STAINMATE:
9626                 if (WhiteOnMove(currentMove)) {
9627                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9628                 } else {
9629                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9630                 }
9631                 break;
9632                 
9633               case MT_STALEMATE:
9634                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9635                 break;
9636             }
9637
9638             break;
9639             
9640           case CMAIL_RESIGN:
9641             if (WhiteOnMove(currentMove)) {
9642                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9643             } else {
9644                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9645             }
9646             break;
9647             
9648           case CMAIL_ACCEPT:
9649             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9650             break;
9651               
9652           default:
9653             break;
9654         }
9655     }
9656
9657     return;
9658 }
9659
9660 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9661 int
9662 CmailLoadGame(f, gameNumber, title, useList)
9663      FILE *f;
9664      int gameNumber;
9665      char *title;
9666      int useList;
9667 {
9668     int retVal;
9669
9670     if (gameNumber > nCmailGames) {
9671         DisplayError(_("No more games in this message"), 0);
9672         return FALSE;
9673     }
9674     if (f == lastLoadGameFP) {
9675         int offset = gameNumber - lastLoadGameNumber;
9676         if (offset == 0) {
9677             cmailMsg[0] = NULLCHAR;
9678             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9679                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9680                 nCmailMovesRegistered--;
9681             }
9682             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9683             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9684                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9685             }
9686         } else {
9687             if (! RegisterMove()) return FALSE;
9688         }
9689     }
9690
9691     retVal = LoadGame(f, gameNumber, title, useList);
9692
9693     /* Make move registered during previous look at this game, if any */
9694     MakeRegisteredMove();
9695
9696     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9697         commentList[currentMove]
9698           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9699         DisplayComment(currentMove - 1, commentList[currentMove]);
9700     }
9701
9702     return retVal;
9703 }
9704
9705 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9706 int
9707 ReloadGame(offset)
9708      int offset;
9709 {
9710     int gameNumber = lastLoadGameNumber + offset;
9711     if (lastLoadGameFP == NULL) {
9712         DisplayError(_("No game has been loaded yet"), 0);
9713         return FALSE;
9714     }
9715     if (gameNumber <= 0) {
9716         DisplayError(_("Can't back up any further"), 0);
9717         return FALSE;
9718     }
9719     if (cmailMsgLoaded) {
9720         return CmailLoadGame(lastLoadGameFP, gameNumber,
9721                              lastLoadGameTitle, lastLoadGameUseList);
9722     } else {
9723         return LoadGame(lastLoadGameFP, gameNumber,
9724                         lastLoadGameTitle, lastLoadGameUseList);
9725     }
9726 }
9727
9728
9729
9730 /* Load the nth game from open file f */
9731 int
9732 LoadGame(f, gameNumber, title, useList)
9733      FILE *f;
9734      int gameNumber;
9735      char *title;
9736      int useList;
9737 {
9738     ChessMove cm;
9739     char buf[MSG_SIZ];
9740     int gn = gameNumber;
9741     ListGame *lg = NULL;
9742     int numPGNTags = 0;
9743     int err;
9744     GameMode oldGameMode;
9745     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9746
9747     if (appData.debugMode) 
9748         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9749
9750     if (gameMode == Training )
9751         SetTrainingModeOff();
9752
9753     oldGameMode = gameMode;
9754     if (gameMode != BeginningOfGame) {
9755       Reset(FALSE, TRUE);
9756     }
9757
9758     gameFileFP = f;
9759     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9760         fclose(lastLoadGameFP);
9761     }
9762
9763     if (useList) {
9764         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9765         
9766         if (lg) {
9767             fseek(f, lg->offset, 0);
9768             GameListHighlight(gameNumber);
9769             gn = 1;
9770         }
9771         else {
9772             DisplayError(_("Game number out of range"), 0);
9773             return FALSE;
9774         }
9775     } else {
9776         GameListDestroy();
9777         if (fseek(f, 0, 0) == -1) {
9778             if (f == lastLoadGameFP ?
9779                 gameNumber == lastLoadGameNumber + 1 :
9780                 gameNumber == 1) {
9781                 gn = 1;
9782             } else {
9783                 DisplayError(_("Can't seek on game file"), 0);
9784                 return FALSE;
9785             }
9786         }
9787     }
9788     lastLoadGameFP = f;
9789     lastLoadGameNumber = gameNumber;
9790     strcpy(lastLoadGameTitle, title);
9791     lastLoadGameUseList = useList;
9792
9793     yynewfile(f);
9794
9795     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9796       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9797                 lg->gameInfo.black);
9798             DisplayTitle(buf);
9799     } else if (*title != NULLCHAR) {
9800         if (gameNumber > 1) {
9801             sprintf(buf, "%s %d", title, gameNumber);
9802             DisplayTitle(buf);
9803         } else {
9804             DisplayTitle(title);
9805         }
9806     }
9807
9808     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9809         gameMode = PlayFromGameFile;
9810         ModeHighlight();
9811     }
9812
9813     currentMove = forwardMostMove = backwardMostMove = 0;
9814     CopyBoard(boards[0], initialPosition);
9815     StopClocks();
9816
9817     /*
9818      * Skip the first gn-1 games in the file.
9819      * Also skip over anything that precedes an identifiable 
9820      * start of game marker, to avoid being confused by 
9821      * garbage at the start of the file.  Currently 
9822      * recognized start of game markers are the move number "1",
9823      * the pattern "gnuchess .* game", the pattern
9824      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9825      * A game that starts with one of the latter two patterns
9826      * will also have a move number 1, possibly
9827      * following a position diagram.
9828      * 5-4-02: Let's try being more lenient and allowing a game to
9829      * start with an unnumbered move.  Does that break anything?
9830      */
9831     cm = lastLoadGameStart = (ChessMove) 0;
9832     while (gn > 0) {
9833         yyboardindex = forwardMostMove;
9834         cm = (ChessMove) yylex();
9835         switch (cm) {
9836           case (ChessMove) 0:
9837             if (cmailMsgLoaded) {
9838                 nCmailGames = CMAIL_MAX_GAMES - gn;
9839             } else {
9840                 Reset(TRUE, TRUE);
9841                 DisplayError(_("Game not found in file"), 0);
9842             }
9843             return FALSE;
9844
9845           case GNUChessGame:
9846           case XBoardGame:
9847             gn--;
9848             lastLoadGameStart = cm;
9849             break;
9850             
9851           case MoveNumberOne:
9852             switch (lastLoadGameStart) {
9853               case GNUChessGame:
9854               case XBoardGame:
9855               case PGNTag:
9856                 break;
9857               case MoveNumberOne:
9858               case (ChessMove) 0:
9859                 gn--;           /* count this game */
9860                 lastLoadGameStart = cm;
9861                 break;
9862               default:
9863                 /* impossible */
9864                 break;
9865             }
9866             break;
9867
9868           case PGNTag:
9869             switch (lastLoadGameStart) {
9870               case GNUChessGame:
9871               case PGNTag:
9872               case MoveNumberOne:
9873               case (ChessMove) 0:
9874                 gn--;           /* count this game */
9875                 lastLoadGameStart = cm;
9876                 break;
9877               case XBoardGame:
9878                 lastLoadGameStart = cm; /* game counted already */
9879                 break;
9880               default:
9881                 /* impossible */
9882                 break;
9883             }
9884             if (gn > 0) {
9885                 do {
9886                     yyboardindex = forwardMostMove;
9887                     cm = (ChessMove) yylex();
9888                 } while (cm == PGNTag || cm == Comment);
9889             }
9890             break;
9891
9892           case WhiteWins:
9893           case BlackWins:
9894           case GameIsDrawn:
9895             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9896                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9897                     != CMAIL_OLD_RESULT) {
9898                     nCmailResults ++ ;
9899                     cmailResult[  CMAIL_MAX_GAMES
9900                                 - gn - 1] = CMAIL_OLD_RESULT;
9901                 }
9902             }
9903             break;
9904
9905           case NormalMove:
9906             /* Only a NormalMove can be at the start of a game
9907              * without a position diagram. */
9908             if (lastLoadGameStart == (ChessMove) 0) {
9909               gn--;
9910               lastLoadGameStart = MoveNumberOne;
9911             }
9912             break;
9913
9914           default:
9915             break;
9916         }
9917     }
9918     
9919     if (appData.debugMode)
9920       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9921
9922     if (cm == XBoardGame) {
9923         /* Skip any header junk before position diagram and/or move 1 */
9924         for (;;) {
9925             yyboardindex = forwardMostMove;
9926             cm = (ChessMove) yylex();
9927
9928             if (cm == (ChessMove) 0 ||
9929                 cm == GNUChessGame || cm == XBoardGame) {
9930                 /* Empty game; pretend end-of-file and handle later */
9931                 cm = (ChessMove) 0;
9932                 break;
9933             }
9934
9935             if (cm == MoveNumberOne || cm == PositionDiagram ||
9936                 cm == PGNTag || cm == Comment)
9937               break;
9938         }
9939     } else if (cm == GNUChessGame) {
9940         if (gameInfo.event != NULL) {
9941             free(gameInfo.event);
9942         }
9943         gameInfo.event = StrSave(yy_text);
9944     }   
9945
9946     startedFromSetupPosition = FALSE;
9947     while (cm == PGNTag) {
9948         if (appData.debugMode) 
9949           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9950         err = ParsePGNTag(yy_text, &gameInfo);
9951         if (!err) numPGNTags++;
9952
9953         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9954         if(gameInfo.variant != oldVariant) {
9955             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9956             InitPosition(TRUE);
9957             oldVariant = gameInfo.variant;
9958             if (appData.debugMode) 
9959               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9960         }
9961
9962
9963         if (gameInfo.fen != NULL) {
9964           Board initial_position;
9965           startedFromSetupPosition = TRUE;
9966           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9967             Reset(TRUE, TRUE);
9968             DisplayError(_("Bad FEN position in file"), 0);
9969             return FALSE;
9970           }
9971           CopyBoard(boards[0], initial_position);
9972           if (blackPlaysFirst) {
9973             currentMove = forwardMostMove = backwardMostMove = 1;
9974             CopyBoard(boards[1], initial_position);
9975             strcpy(moveList[0], "");
9976             strcpy(parseList[0], "");
9977             timeRemaining[0][1] = whiteTimeRemaining;
9978             timeRemaining[1][1] = blackTimeRemaining;
9979             if (commentList[0] != NULL) {
9980               commentList[1] = commentList[0];
9981               commentList[0] = NULL;
9982             }
9983           } else {
9984             currentMove = forwardMostMove = backwardMostMove = 0;
9985           }
9986           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9987           {   int i;
9988               initialRulePlies = FENrulePlies;
9989               for( i=0; i< nrCastlingRights; i++ )
9990                   initialRights[i] = initial_position[CASTLING][i];
9991           }
9992           yyboardindex = forwardMostMove;
9993           free(gameInfo.fen);
9994           gameInfo.fen = NULL;
9995         }
9996
9997         yyboardindex = forwardMostMove;
9998         cm = (ChessMove) yylex();
9999
10000         /* Handle comments interspersed among the tags */
10001         while (cm == Comment) {
10002             char *p;
10003             if (appData.debugMode) 
10004               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10005             p = yy_text;
10006             AppendComment(currentMove, p, FALSE);
10007             yyboardindex = forwardMostMove;
10008             cm = (ChessMove) yylex();
10009         }
10010     }
10011
10012     /* don't rely on existence of Event tag since if game was
10013      * pasted from clipboard the Event tag may not exist
10014      */
10015     if (numPGNTags > 0){
10016         char *tags;
10017         if (gameInfo.variant == VariantNormal) {
10018           gameInfo.variant = StringToVariant(gameInfo.event);
10019         }
10020         if (!matchMode) {
10021           if( appData.autoDisplayTags ) {
10022             tags = PGNTags(&gameInfo);
10023             TagsPopUp(tags, CmailMsg());
10024             free(tags);
10025           }
10026         }
10027     } else {
10028         /* Make something up, but don't display it now */
10029         SetGameInfo();
10030         TagsPopDown();
10031     }
10032
10033     if (cm == PositionDiagram) {
10034         int i, j;
10035         char *p;
10036         Board initial_position;
10037
10038         if (appData.debugMode)
10039           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10040
10041         if (!startedFromSetupPosition) {
10042             p = yy_text;
10043             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10044               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10045                 switch (*p) {
10046                   case '[':
10047                   case '-':
10048                   case ' ':
10049                   case '\t':
10050                   case '\n':
10051                   case '\r':
10052                     break;
10053                   default:
10054                     initial_position[i][j++] = CharToPiece(*p);
10055                     break;
10056                 }
10057             while (*p == ' ' || *p == '\t' ||
10058                    *p == '\n' || *p == '\r') p++;
10059         
10060             if (strncmp(p, "black", strlen("black"))==0)
10061               blackPlaysFirst = TRUE;
10062             else
10063               blackPlaysFirst = FALSE;
10064             startedFromSetupPosition = TRUE;
10065         
10066             CopyBoard(boards[0], initial_position);
10067             if (blackPlaysFirst) {
10068                 currentMove = forwardMostMove = backwardMostMove = 1;
10069                 CopyBoard(boards[1], initial_position);
10070                 strcpy(moveList[0], "");
10071                 strcpy(parseList[0], "");
10072                 timeRemaining[0][1] = whiteTimeRemaining;
10073                 timeRemaining[1][1] = blackTimeRemaining;
10074                 if (commentList[0] != NULL) {
10075                     commentList[1] = commentList[0];
10076                     commentList[0] = NULL;
10077                 }
10078             } else {
10079                 currentMove = forwardMostMove = backwardMostMove = 0;
10080             }
10081         }
10082         yyboardindex = forwardMostMove;
10083         cm = (ChessMove) yylex();
10084     }
10085
10086     if (first.pr == NoProc) {
10087         StartChessProgram(&first);
10088     }
10089     InitChessProgram(&first, FALSE);
10090     SendToProgram("force\n", &first);
10091     if (startedFromSetupPosition) {
10092         SendBoard(&first, forwardMostMove);
10093     if (appData.debugMode) {
10094         fprintf(debugFP, "Load Game\n");
10095     }
10096         DisplayBothClocks();
10097     }      
10098
10099     /* [HGM] server: flag to write setup moves in broadcast file as one */
10100     loadFlag = appData.suppressLoadMoves;
10101
10102     while (cm == Comment) {
10103         char *p;
10104         if (appData.debugMode) 
10105           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10106         p = yy_text;
10107         AppendComment(currentMove, p, FALSE);
10108         yyboardindex = forwardMostMove;
10109         cm = (ChessMove) yylex();
10110     }
10111
10112     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10113         cm == WhiteWins || cm == BlackWins ||
10114         cm == GameIsDrawn || cm == GameUnfinished) {
10115         DisplayMessage("", _("No moves in game"));
10116         if (cmailMsgLoaded) {
10117             if (appData.debugMode)
10118               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10119             ClearHighlights();
10120             flipView = FALSE;
10121         }
10122         DrawPosition(FALSE, boards[currentMove]);
10123         DisplayBothClocks();
10124         gameMode = EditGame;
10125         ModeHighlight();
10126         gameFileFP = NULL;
10127         cmailOldMove = 0;
10128         return TRUE;
10129     }
10130
10131     // [HGM] PV info: routine tests if comment empty
10132     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10133         DisplayComment(currentMove - 1, commentList[currentMove]);
10134     }
10135     if (!matchMode && appData.timeDelay != 0) 
10136       DrawPosition(FALSE, boards[currentMove]);
10137
10138     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10139       programStats.ok_to_send = 1;
10140     }
10141
10142     /* if the first token after the PGN tags is a move
10143      * and not move number 1, retrieve it from the parser 
10144      */
10145     if (cm != MoveNumberOne)
10146         LoadGameOneMove(cm);
10147
10148     /* load the remaining moves from the file */
10149     while (LoadGameOneMove((ChessMove)0)) {
10150       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10151       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10152     }
10153
10154     /* rewind to the start of the game */
10155     currentMove = backwardMostMove;
10156
10157     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10158
10159     if (oldGameMode == AnalyzeFile ||
10160         oldGameMode == AnalyzeMode) {
10161       AnalyzeFileEvent();
10162     }
10163
10164     if (matchMode || appData.timeDelay == 0) {
10165       ToEndEvent();
10166       gameMode = EditGame;
10167       ModeHighlight();
10168     } else if (appData.timeDelay > 0) {
10169       AutoPlayGameLoop();
10170     }
10171
10172     if (appData.debugMode) 
10173         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10174
10175     loadFlag = 0; /* [HGM] true game starts */
10176     return TRUE;
10177 }
10178
10179 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10180 int
10181 ReloadPosition(offset)
10182      int offset;
10183 {
10184     int positionNumber = lastLoadPositionNumber + offset;
10185     if (lastLoadPositionFP == NULL) {
10186         DisplayError(_("No position has been loaded yet"), 0);
10187         return FALSE;
10188     }
10189     if (positionNumber <= 0) {
10190         DisplayError(_("Can't back up any further"), 0);
10191         return FALSE;
10192     }
10193     return LoadPosition(lastLoadPositionFP, positionNumber,
10194                         lastLoadPositionTitle);
10195 }
10196
10197 /* Load the nth position from the given file */
10198 int
10199 LoadPositionFromFile(filename, n, title)
10200      char *filename;
10201      int n;
10202      char *title;
10203 {
10204     FILE *f;
10205     char buf[MSG_SIZ];
10206
10207     if (strcmp(filename, "-") == 0) {
10208         return LoadPosition(stdin, n, "stdin");
10209     } else {
10210         f = fopen(filename, "rb");
10211         if (f == NULL) {
10212             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10213             DisplayError(buf, errno);
10214             return FALSE;
10215         } else {
10216             return LoadPosition(f, n, title);
10217         }
10218     }
10219 }
10220
10221 /* Load the nth position from the given open file, and close it */
10222 int
10223 LoadPosition(f, positionNumber, title)
10224      FILE *f;
10225      int positionNumber;
10226      char *title;
10227 {
10228     char *p, line[MSG_SIZ];
10229     Board initial_position;
10230     int i, j, fenMode, pn;
10231     
10232     if (gameMode == Training )
10233         SetTrainingModeOff();
10234
10235     if (gameMode != BeginningOfGame) {
10236         Reset(FALSE, TRUE);
10237     }
10238     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10239         fclose(lastLoadPositionFP);
10240     }
10241     if (positionNumber == 0) positionNumber = 1;
10242     lastLoadPositionFP = f;
10243     lastLoadPositionNumber = positionNumber;
10244     strcpy(lastLoadPositionTitle, title);
10245     if (first.pr == NoProc) {
10246       StartChessProgram(&first);
10247       InitChessProgram(&first, FALSE);
10248     }    
10249     pn = positionNumber;
10250     if (positionNumber < 0) {
10251         /* Negative position number means to seek to that byte offset */
10252         if (fseek(f, -positionNumber, 0) == -1) {
10253             DisplayError(_("Can't seek on position file"), 0);
10254             return FALSE;
10255         };
10256         pn = 1;
10257     } else {
10258         if (fseek(f, 0, 0) == -1) {
10259             if (f == lastLoadPositionFP ?
10260                 positionNumber == lastLoadPositionNumber + 1 :
10261                 positionNumber == 1) {
10262                 pn = 1;
10263             } else {
10264                 DisplayError(_("Can't seek on position file"), 0);
10265                 return FALSE;
10266             }
10267         }
10268     }
10269     /* See if this file is FEN or old-style xboard */
10270     if (fgets(line, MSG_SIZ, f) == NULL) {
10271         DisplayError(_("Position not found in file"), 0);
10272         return FALSE;
10273     }
10274     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10275     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10276
10277     if (pn >= 2) {
10278         if (fenMode || line[0] == '#') pn--;
10279         while (pn > 0) {
10280             /* skip positions before number pn */
10281             if (fgets(line, MSG_SIZ, f) == NULL) {
10282                 Reset(TRUE, TRUE);
10283                 DisplayError(_("Position not found in file"), 0);
10284                 return FALSE;
10285             }
10286             if (fenMode || line[0] == '#') pn--;
10287         }
10288     }
10289
10290     if (fenMode) {
10291         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10292             DisplayError(_("Bad FEN position in file"), 0);
10293             return FALSE;
10294         }
10295     } else {
10296         (void) fgets(line, MSG_SIZ, f);
10297         (void) fgets(line, MSG_SIZ, f);
10298     
10299         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10300             (void) fgets(line, MSG_SIZ, f);
10301             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10302                 if (*p == ' ')
10303                   continue;
10304                 initial_position[i][j++] = CharToPiece(*p);
10305             }
10306         }
10307     
10308         blackPlaysFirst = FALSE;
10309         if (!feof(f)) {
10310             (void) fgets(line, MSG_SIZ, f);
10311             if (strncmp(line, "black", strlen("black"))==0)
10312               blackPlaysFirst = TRUE;
10313         }
10314     }
10315     startedFromSetupPosition = TRUE;
10316     
10317     SendToProgram("force\n", &first);
10318     CopyBoard(boards[0], initial_position);
10319     if (blackPlaysFirst) {
10320         currentMove = forwardMostMove = backwardMostMove = 1;
10321         strcpy(moveList[0], "");
10322         strcpy(parseList[0], "");
10323         CopyBoard(boards[1], initial_position);
10324         DisplayMessage("", _("Black to play"));
10325     } else {
10326         currentMove = forwardMostMove = backwardMostMove = 0;
10327         DisplayMessage("", _("White to play"));
10328     }
10329     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10330     SendBoard(&first, forwardMostMove);
10331     if (appData.debugMode) {
10332 int i, j;
10333   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10334   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10335         fprintf(debugFP, "Load Position\n");
10336     }
10337
10338     if (positionNumber > 1) {
10339         sprintf(line, "%s %d", title, positionNumber);
10340         DisplayTitle(line);
10341     } else {
10342         DisplayTitle(title);
10343     }
10344     gameMode = EditGame;
10345     ModeHighlight();
10346     ResetClocks();
10347     timeRemaining[0][1] = whiteTimeRemaining;
10348     timeRemaining[1][1] = blackTimeRemaining;
10349     DrawPosition(FALSE, boards[currentMove]);
10350    
10351     return TRUE;
10352 }
10353
10354
10355 void
10356 CopyPlayerNameIntoFileName(dest, src)
10357      char **dest, *src;
10358 {
10359     while (*src != NULLCHAR && *src != ',') {
10360         if (*src == ' ') {
10361             *(*dest)++ = '_';
10362             src++;
10363         } else {
10364             *(*dest)++ = *src++;
10365         }
10366     }
10367 }
10368
10369 char *DefaultFileName(ext)
10370      char *ext;
10371 {
10372     static char def[MSG_SIZ];
10373     char *p;
10374
10375     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10376         p = def;
10377         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10378         *p++ = '-';
10379         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10380         *p++ = '.';
10381         strcpy(p, ext);
10382     } else {
10383         def[0] = NULLCHAR;
10384     }
10385     return def;
10386 }
10387
10388 /* Save the current game to the given file */
10389 int
10390 SaveGameToFile(filename, append)
10391      char *filename;
10392      int append;
10393 {
10394     FILE *f;
10395     char buf[MSG_SIZ];
10396
10397     if (strcmp(filename, "-") == 0) {
10398         return SaveGame(stdout, 0, NULL);
10399     } else {
10400         f = fopen(filename, append ? "a" : "w");
10401         if (f == NULL) {
10402             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10403             DisplayError(buf, errno);
10404             return FALSE;
10405         } else {
10406             return SaveGame(f, 0, NULL);
10407         }
10408     }
10409 }
10410
10411 char *
10412 SavePart(str)
10413      char *str;
10414 {
10415     static char buf[MSG_SIZ];
10416     char *p;
10417     
10418     p = strchr(str, ' ');
10419     if (p == NULL) return str;
10420     strncpy(buf, str, p - str);
10421     buf[p - str] = NULLCHAR;
10422     return buf;
10423 }
10424
10425 #define PGN_MAX_LINE 75
10426
10427 #define PGN_SIDE_WHITE  0
10428 #define PGN_SIDE_BLACK  1
10429
10430 /* [AS] */
10431 static int FindFirstMoveOutOfBook( int side )
10432 {
10433     int result = -1;
10434
10435     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10436         int index = backwardMostMove;
10437         int has_book_hit = 0;
10438
10439         if( (index % 2) != side ) {
10440             index++;
10441         }
10442
10443         while( index < forwardMostMove ) {
10444             /* Check to see if engine is in book */
10445             int depth = pvInfoList[index].depth;
10446             int score = pvInfoList[index].score;
10447             int in_book = 0;
10448
10449             if( depth <= 2 ) {
10450                 in_book = 1;
10451             }
10452             else if( score == 0 && depth == 63 ) {
10453                 in_book = 1; /* Zappa */
10454             }
10455             else if( score == 2 && depth == 99 ) {
10456                 in_book = 1; /* Abrok */
10457             }
10458
10459             has_book_hit += in_book;
10460
10461             if( ! in_book ) {
10462                 result = index;
10463
10464                 break;
10465             }
10466
10467             index += 2;
10468         }
10469     }
10470
10471     return result;
10472 }
10473
10474 /* [AS] */
10475 void GetOutOfBookInfo( char * buf )
10476 {
10477     int oob[2];
10478     int i;
10479     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10480
10481     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10482     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10483
10484     *buf = '\0';
10485
10486     if( oob[0] >= 0 || oob[1] >= 0 ) {
10487         for( i=0; i<2; i++ ) {
10488             int idx = oob[i];
10489
10490             if( idx >= 0 ) {
10491                 if( i > 0 && oob[0] >= 0 ) {
10492                     strcat( buf, "   " );
10493                 }
10494
10495                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10496                 sprintf( buf+strlen(buf), "%s%.2f", 
10497                     pvInfoList[idx].score >= 0 ? "+" : "",
10498                     pvInfoList[idx].score / 100.0 );
10499             }
10500         }
10501     }
10502 }
10503
10504 /* Save game in PGN style and close the file */
10505 int
10506 SaveGamePGN(f)
10507      FILE *f;
10508 {
10509     int i, offset, linelen, newblock;
10510     time_t tm;
10511 //    char *movetext;
10512     char numtext[32];
10513     int movelen, numlen, blank;
10514     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10515
10516     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10517     
10518     tm = time((time_t *) NULL);
10519     
10520     PrintPGNTags(f, &gameInfo);
10521     
10522     if (backwardMostMove > 0 || startedFromSetupPosition) {
10523         char *fen = PositionToFEN(backwardMostMove, NULL);
10524         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10525         fprintf(f, "\n{--------------\n");
10526         PrintPosition(f, backwardMostMove);
10527         fprintf(f, "--------------}\n");
10528         free(fen);
10529     }
10530     else {
10531         /* [AS] Out of book annotation */
10532         if( appData.saveOutOfBookInfo ) {
10533             char buf[64];
10534
10535             GetOutOfBookInfo( buf );
10536
10537             if( buf[0] != '\0' ) {
10538                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10539             }
10540         }
10541
10542         fprintf(f, "\n");
10543     }
10544
10545     i = backwardMostMove;
10546     linelen = 0;
10547     newblock = TRUE;
10548
10549     while (i < forwardMostMove) {
10550         /* Print comments preceding this move */
10551         if (commentList[i] != NULL) {
10552             if (linelen > 0) fprintf(f, "\n");
10553             fprintf(f, "%s", commentList[i]);
10554             linelen = 0;
10555             newblock = TRUE;
10556         }
10557
10558         /* Format move number */
10559         if ((i % 2) == 0) {
10560             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10561         } else {
10562             if (newblock) {
10563                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10564             } else {
10565                 numtext[0] = NULLCHAR;
10566             }
10567         }
10568         numlen = strlen(numtext);
10569         newblock = FALSE;
10570
10571         /* Print move number */
10572         blank = linelen > 0 && numlen > 0;
10573         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10574             fprintf(f, "\n");
10575             linelen = 0;
10576             blank = 0;
10577         }
10578         if (blank) {
10579             fprintf(f, " ");
10580             linelen++;
10581         }
10582         fprintf(f, "%s", numtext);
10583         linelen += numlen;
10584
10585         /* Get move */
10586         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10587         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10588
10589         /* Print move */
10590         blank = linelen > 0 && movelen > 0;
10591         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10592             fprintf(f, "\n");
10593             linelen = 0;
10594             blank = 0;
10595         }
10596         if (blank) {
10597             fprintf(f, " ");
10598             linelen++;
10599         }
10600         fprintf(f, "%s", move_buffer);
10601         linelen += movelen;
10602
10603         /* [AS] Add PV info if present */
10604         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10605             /* [HGM] add time */
10606             char buf[MSG_SIZ]; int seconds;
10607
10608             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10609
10610             if( seconds <= 0) buf[0] = 0; else
10611             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10612                 seconds = (seconds + 4)/10; // round to full seconds
10613                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10614                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10615             }
10616
10617             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10618                 pvInfoList[i].score >= 0 ? "+" : "",
10619                 pvInfoList[i].score / 100.0,
10620                 pvInfoList[i].depth,
10621                 buf );
10622
10623             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10624
10625             /* Print score/depth */
10626             blank = linelen > 0 && movelen > 0;
10627             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10628                 fprintf(f, "\n");
10629                 linelen = 0;
10630                 blank = 0;
10631             }
10632             if (blank) {
10633                 fprintf(f, " ");
10634                 linelen++;
10635             }
10636             fprintf(f, "%s", move_buffer);
10637             linelen += movelen;
10638         }
10639
10640         i++;
10641     }
10642     
10643     /* Start a new line */
10644     if (linelen > 0) fprintf(f, "\n");
10645
10646     /* Print comments after last move */
10647     if (commentList[i] != NULL) {
10648         fprintf(f, "%s\n", commentList[i]);
10649     }
10650
10651     /* Print result */
10652     if (gameInfo.resultDetails != NULL &&
10653         gameInfo.resultDetails[0] != NULLCHAR) {
10654         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10655                 PGNResult(gameInfo.result));
10656     } else {
10657         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10658     }
10659
10660     fclose(f);
10661     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10662     return TRUE;
10663 }
10664
10665 /* Save game in old style and close the file */
10666 int
10667 SaveGameOldStyle(f)
10668      FILE *f;
10669 {
10670     int i, offset;
10671     time_t tm;
10672     
10673     tm = time((time_t *) NULL);
10674     
10675     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10676     PrintOpponents(f);
10677     
10678     if (backwardMostMove > 0 || startedFromSetupPosition) {
10679         fprintf(f, "\n[--------------\n");
10680         PrintPosition(f, backwardMostMove);
10681         fprintf(f, "--------------]\n");
10682     } else {
10683         fprintf(f, "\n");
10684     }
10685
10686     i = backwardMostMove;
10687     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10688
10689     while (i < forwardMostMove) {
10690         if (commentList[i] != NULL) {
10691             fprintf(f, "[%s]\n", commentList[i]);
10692         }
10693
10694         if ((i % 2) == 1) {
10695             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10696             i++;
10697         } else {
10698             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10699             i++;
10700             if (commentList[i] != NULL) {
10701                 fprintf(f, "\n");
10702                 continue;
10703             }
10704             if (i >= forwardMostMove) {
10705                 fprintf(f, "\n");
10706                 break;
10707             }
10708             fprintf(f, "%s\n", parseList[i]);
10709             i++;
10710         }
10711     }
10712     
10713     if (commentList[i] != NULL) {
10714         fprintf(f, "[%s]\n", commentList[i]);
10715     }
10716
10717     /* This isn't really the old style, but it's close enough */
10718     if (gameInfo.resultDetails != NULL &&
10719         gameInfo.resultDetails[0] != NULLCHAR) {
10720         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10721                 gameInfo.resultDetails);
10722     } else {
10723         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10724     }
10725
10726     fclose(f);
10727     return TRUE;
10728 }
10729
10730 /* Save the current game to open file f and close the file */
10731 int
10732 SaveGame(f, dummy, dummy2)
10733      FILE *f;
10734      int dummy;
10735      char *dummy2;
10736 {
10737     if (gameMode == EditPosition) EditPositionDone(TRUE);
10738     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10739     if (appData.oldSaveStyle)
10740       return SaveGameOldStyle(f);
10741     else
10742       return SaveGamePGN(f);
10743 }
10744
10745 /* Save the current position to the given file */
10746 int
10747 SavePositionToFile(filename)
10748      char *filename;
10749 {
10750     FILE *f;
10751     char buf[MSG_SIZ];
10752
10753     if (strcmp(filename, "-") == 0) {
10754         return SavePosition(stdout, 0, NULL);
10755     } else {
10756         f = fopen(filename, "a");
10757         if (f == NULL) {
10758             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10759             DisplayError(buf, errno);
10760             return FALSE;
10761         } else {
10762             SavePosition(f, 0, NULL);
10763             return TRUE;
10764         }
10765     }
10766 }
10767
10768 /* Save the current position to the given open file and close the file */
10769 int
10770 SavePosition(f, dummy, dummy2)
10771      FILE *f;
10772      int dummy;
10773      char *dummy2;
10774 {
10775     time_t tm;
10776     char *fen;
10777     
10778     if (gameMode == EditPosition) EditPositionDone(TRUE);
10779     if (appData.oldSaveStyle) {
10780         tm = time((time_t *) NULL);
10781     
10782         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10783         PrintOpponents(f);
10784         fprintf(f, "[--------------\n");
10785         PrintPosition(f, currentMove);
10786         fprintf(f, "--------------]\n");
10787     } else {
10788         fen = PositionToFEN(currentMove, NULL);
10789         fprintf(f, "%s\n", fen);
10790         free(fen);
10791     }
10792     fclose(f);
10793     return TRUE;
10794 }
10795
10796 void
10797 ReloadCmailMsgEvent(unregister)
10798      int unregister;
10799 {
10800 #if !WIN32
10801     static char *inFilename = NULL;
10802     static char *outFilename;
10803     int i;
10804     struct stat inbuf, outbuf;
10805     int status;
10806     
10807     /* Any registered moves are unregistered if unregister is set, */
10808     /* i.e. invoked by the signal handler */
10809     if (unregister) {
10810         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10811             cmailMoveRegistered[i] = FALSE;
10812             if (cmailCommentList[i] != NULL) {
10813                 free(cmailCommentList[i]);
10814                 cmailCommentList[i] = NULL;
10815             }
10816         }
10817         nCmailMovesRegistered = 0;
10818     }
10819
10820     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10821         cmailResult[i] = CMAIL_NOT_RESULT;
10822     }
10823     nCmailResults = 0;
10824
10825     if (inFilename == NULL) {
10826         /* Because the filenames are static they only get malloced once  */
10827         /* and they never get freed                                      */
10828         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10829         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10830
10831         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10832         sprintf(outFilename, "%s.out", appData.cmailGameName);
10833     }
10834     
10835     status = stat(outFilename, &outbuf);
10836     if (status < 0) {
10837         cmailMailedMove = FALSE;
10838     } else {
10839         status = stat(inFilename, &inbuf);
10840         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10841     }
10842     
10843     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10844        counts the games, notes how each one terminated, etc.
10845        
10846        It would be nice to remove this kludge and instead gather all
10847        the information while building the game list.  (And to keep it
10848        in the game list nodes instead of having a bunch of fixed-size
10849        parallel arrays.)  Note this will require getting each game's
10850        termination from the PGN tags, as the game list builder does
10851        not process the game moves.  --mann
10852        */
10853     cmailMsgLoaded = TRUE;
10854     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10855     
10856     /* Load first game in the file or popup game menu */
10857     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10858
10859 #endif /* !WIN32 */
10860     return;
10861 }
10862
10863 int
10864 RegisterMove()
10865 {
10866     FILE *f;
10867     char string[MSG_SIZ];
10868
10869     if (   cmailMailedMove
10870         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10871         return TRUE;            /* Allow free viewing  */
10872     }
10873
10874     /* Unregister move to ensure that we don't leave RegisterMove        */
10875     /* with the move registered when the conditions for registering no   */
10876     /* longer hold                                                       */
10877     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10878         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10879         nCmailMovesRegistered --;
10880
10881         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10882           {
10883               free(cmailCommentList[lastLoadGameNumber - 1]);
10884               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10885           }
10886     }
10887
10888     if (cmailOldMove == -1) {
10889         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10890         return FALSE;
10891     }
10892
10893     if (currentMove > cmailOldMove + 1) {
10894         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10895         return FALSE;
10896     }
10897
10898     if (currentMove < cmailOldMove) {
10899         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10900         return FALSE;
10901     }
10902
10903     if (forwardMostMove > currentMove) {
10904         /* Silently truncate extra moves */
10905         TruncateGame();
10906     }
10907
10908     if (   (currentMove == cmailOldMove + 1)
10909         || (   (currentMove == cmailOldMove)
10910             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10911                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10912         if (gameInfo.result != GameUnfinished) {
10913             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10914         }
10915
10916         if (commentList[currentMove] != NULL) {
10917             cmailCommentList[lastLoadGameNumber - 1]
10918               = StrSave(commentList[currentMove]);
10919         }
10920         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10921
10922         if (appData.debugMode)
10923           fprintf(debugFP, "Saving %s for game %d\n",
10924                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10925
10926         sprintf(string,
10927                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10928         
10929         f = fopen(string, "w");
10930         if (appData.oldSaveStyle) {
10931             SaveGameOldStyle(f); /* also closes the file */
10932             
10933             sprintf(string, "%s.pos.out", appData.cmailGameName);
10934             f = fopen(string, "w");
10935             SavePosition(f, 0, NULL); /* also closes the file */
10936         } else {
10937             fprintf(f, "{--------------\n");
10938             PrintPosition(f, currentMove);
10939             fprintf(f, "--------------}\n\n");
10940             
10941             SaveGame(f, 0, NULL); /* also closes the file*/
10942         }
10943         
10944         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10945         nCmailMovesRegistered ++;
10946     } else if (nCmailGames == 1) {
10947         DisplayError(_("You have not made a move yet"), 0);
10948         return FALSE;
10949     }
10950
10951     return TRUE;
10952 }
10953
10954 void
10955 MailMoveEvent()
10956 {
10957 #if !WIN32
10958     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10959     FILE *commandOutput;
10960     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10961     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10962     int nBuffers;
10963     int i;
10964     int archived;
10965     char *arcDir;
10966
10967     if (! cmailMsgLoaded) {
10968         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10969         return;
10970     }
10971
10972     if (nCmailGames == nCmailResults) {
10973         DisplayError(_("No unfinished games"), 0);
10974         return;
10975     }
10976
10977 #if CMAIL_PROHIBIT_REMAIL
10978     if (cmailMailedMove) {
10979         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);
10980         DisplayError(msg, 0);
10981         return;
10982     }
10983 #endif
10984
10985     if (! (cmailMailedMove || RegisterMove())) return;
10986     
10987     if (   cmailMailedMove
10988         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10989         sprintf(string, partCommandString,
10990                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10991         commandOutput = popen(string, "r");
10992
10993         if (commandOutput == NULL) {
10994             DisplayError(_("Failed to invoke cmail"), 0);
10995         } else {
10996             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10997                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10998             }
10999             if (nBuffers > 1) {
11000                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11001                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11002                 nBytes = MSG_SIZ - 1;
11003             } else {
11004                 (void) memcpy(msg, buffer, nBytes);
11005             }
11006             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11007
11008             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11009                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11010
11011                 archived = TRUE;
11012                 for (i = 0; i < nCmailGames; i ++) {
11013                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11014                         archived = FALSE;
11015                     }
11016                 }
11017                 if (   archived
11018                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11019                         != NULL)) {
11020                     sprintf(buffer, "%s/%s.%s.archive",
11021                             arcDir,
11022                             appData.cmailGameName,
11023                             gameInfo.date);
11024                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11025                     cmailMsgLoaded = FALSE;
11026                 }
11027             }
11028
11029             DisplayInformation(msg);
11030             pclose(commandOutput);
11031         }
11032     } else {
11033         if ((*cmailMsg) != '\0') {
11034             DisplayInformation(cmailMsg);
11035         }
11036     }
11037
11038     return;
11039 #endif /* !WIN32 */
11040 }
11041
11042 char *
11043 CmailMsg()
11044 {
11045 #if WIN32
11046     return NULL;
11047 #else
11048     int  prependComma = 0;
11049     char number[5];
11050     char string[MSG_SIZ];       /* Space for game-list */
11051     int  i;
11052     
11053     if (!cmailMsgLoaded) return "";
11054
11055     if (cmailMailedMove) {
11056         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11057     } else {
11058         /* Create a list of games left */
11059         sprintf(string, "[");
11060         for (i = 0; i < nCmailGames; i ++) {
11061             if (! (   cmailMoveRegistered[i]
11062                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11063                 if (prependComma) {
11064                     sprintf(number, ",%d", i + 1);
11065                 } else {
11066                     sprintf(number, "%d", i + 1);
11067                     prependComma = 1;
11068                 }
11069                 
11070                 strcat(string, number);
11071             }
11072         }
11073         strcat(string, "]");
11074
11075         if (nCmailMovesRegistered + nCmailResults == 0) {
11076             switch (nCmailGames) {
11077               case 1:
11078                 sprintf(cmailMsg,
11079                         _("Still need to make move for game\n"));
11080                 break;
11081                 
11082               case 2:
11083                 sprintf(cmailMsg,
11084                         _("Still need to make moves for both games\n"));
11085                 break;
11086                 
11087               default:
11088                 sprintf(cmailMsg,
11089                         _("Still need to make moves for all %d games\n"),
11090                         nCmailGames);
11091                 break;
11092             }
11093         } else {
11094             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11095               case 1:
11096                 sprintf(cmailMsg,
11097                         _("Still need to make a move for game %s\n"),
11098                         string);
11099                 break;
11100                 
11101               case 0:
11102                 if (nCmailResults == nCmailGames) {
11103                     sprintf(cmailMsg, _("No unfinished games\n"));
11104                 } else {
11105                     sprintf(cmailMsg, _("Ready to send mail\n"));
11106                 }
11107                 break;
11108                 
11109               default:
11110                 sprintf(cmailMsg,
11111                         _("Still need to make moves for games %s\n"),
11112                         string);
11113             }
11114         }
11115     }
11116     return cmailMsg;
11117 #endif /* WIN32 */
11118 }
11119
11120 void
11121 ResetGameEvent()
11122 {
11123     if (gameMode == Training)
11124       SetTrainingModeOff();
11125
11126     Reset(TRUE, TRUE);
11127     cmailMsgLoaded = FALSE;
11128     if (appData.icsActive) {
11129       SendToICS(ics_prefix);
11130       SendToICS("refresh\n");
11131     }
11132 }
11133
11134 void
11135 ExitEvent(status)
11136      int status;
11137 {
11138     exiting++;
11139     if (exiting > 2) {
11140       /* Give up on clean exit */
11141       exit(status);
11142     }
11143     if (exiting > 1) {
11144       /* Keep trying for clean exit */
11145       return;
11146     }
11147
11148     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11149
11150     if (telnetISR != NULL) {
11151       RemoveInputSource(telnetISR);
11152     }
11153     if (icsPR != NoProc) {
11154       DestroyChildProcess(icsPR, TRUE);
11155     }
11156
11157     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11158     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11159
11160     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11161     /* make sure this other one finishes before killing it!                  */
11162     if(endingGame) { int count = 0;
11163         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11164         while(endingGame && count++ < 10) DoSleep(1);
11165         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11166     }
11167
11168     /* Kill off chess programs */
11169     if (first.pr != NoProc) {
11170         ExitAnalyzeMode();
11171         
11172         DoSleep( appData.delayBeforeQuit );
11173         SendToProgram("quit\n", &first);
11174         DoSleep( appData.delayAfterQuit );
11175         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11176     }
11177     if (second.pr != NoProc) {
11178         DoSleep( appData.delayBeforeQuit );
11179         SendToProgram("quit\n", &second);
11180         DoSleep( appData.delayAfterQuit );
11181         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11182     }
11183     if (first.isr != NULL) {
11184         RemoveInputSource(first.isr);
11185     }
11186     if (second.isr != NULL) {
11187         RemoveInputSource(second.isr);
11188     }
11189
11190     ShutDownFrontEnd();
11191     exit(status);
11192 }
11193
11194 void
11195 PauseEvent()
11196 {
11197     if (appData.debugMode)
11198         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11199     if (pausing) {
11200         pausing = FALSE;
11201         ModeHighlight();
11202         if (gameMode == MachinePlaysWhite ||
11203             gameMode == MachinePlaysBlack) {
11204             StartClocks();
11205         } else {
11206             DisplayBothClocks();
11207         }
11208         if (gameMode == PlayFromGameFile) {
11209             if (appData.timeDelay >= 0) 
11210                 AutoPlayGameLoop();
11211         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11212             Reset(FALSE, TRUE);
11213             SendToICS(ics_prefix);
11214             SendToICS("refresh\n");
11215         } else if (currentMove < forwardMostMove) {
11216             ForwardInner(forwardMostMove);
11217         }
11218         pauseExamInvalid = FALSE;
11219     } else {
11220         switch (gameMode) {
11221           default:
11222             return;
11223           case IcsExamining:
11224             pauseExamForwardMostMove = forwardMostMove;
11225             pauseExamInvalid = FALSE;
11226             /* fall through */
11227           case IcsObserving:
11228           case IcsPlayingWhite:
11229           case IcsPlayingBlack:
11230             pausing = TRUE;
11231             ModeHighlight();
11232             return;
11233           case PlayFromGameFile:
11234             (void) StopLoadGameTimer();
11235             pausing = TRUE;
11236             ModeHighlight();
11237             break;
11238           case BeginningOfGame:
11239             if (appData.icsActive) return;
11240             /* else fall through */
11241           case MachinePlaysWhite:
11242           case MachinePlaysBlack:
11243           case TwoMachinesPlay:
11244             if (forwardMostMove == 0)
11245               return;           /* don't pause if no one has moved */
11246             if ((gameMode == MachinePlaysWhite &&
11247                  !WhiteOnMove(forwardMostMove)) ||
11248                 (gameMode == MachinePlaysBlack &&
11249                  WhiteOnMove(forwardMostMove))) {
11250                 StopClocks();
11251             }
11252             pausing = TRUE;
11253             ModeHighlight();
11254             break;
11255         }
11256     }
11257 }
11258
11259 void
11260 EditCommentEvent()
11261 {
11262     char title[MSG_SIZ];
11263
11264     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11265         strcpy(title, _("Edit comment"));
11266     } else {
11267         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11268                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11269                 parseList[currentMove - 1]);
11270     }
11271
11272     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11273 }
11274
11275
11276 void
11277 EditTagsEvent()
11278 {
11279     char *tags = PGNTags(&gameInfo);
11280     EditTagsPopUp(tags);
11281     free(tags);
11282 }
11283
11284 void
11285 AnalyzeModeEvent()
11286 {
11287     if (appData.noChessProgram || gameMode == AnalyzeMode)
11288       return;
11289
11290     if (gameMode != AnalyzeFile) {
11291         if (!appData.icsEngineAnalyze) {
11292                EditGameEvent();
11293                if (gameMode != EditGame) return;
11294         }
11295         ResurrectChessProgram();
11296         SendToProgram("analyze\n", &first);
11297         first.analyzing = TRUE;
11298         /*first.maybeThinking = TRUE;*/
11299         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11300         EngineOutputPopUp();
11301     }
11302     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11303     pausing = FALSE;
11304     ModeHighlight();
11305     SetGameInfo();
11306
11307     StartAnalysisClock();
11308     GetTimeMark(&lastNodeCountTime);
11309     lastNodeCount = 0;
11310 }
11311
11312 void
11313 AnalyzeFileEvent()
11314 {
11315     if (appData.noChessProgram || gameMode == AnalyzeFile)
11316       return;
11317
11318     if (gameMode != AnalyzeMode) {
11319         EditGameEvent();
11320         if (gameMode != EditGame) return;
11321         ResurrectChessProgram();
11322         SendToProgram("analyze\n", &first);
11323         first.analyzing = TRUE;
11324         /*first.maybeThinking = TRUE;*/
11325         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11326         EngineOutputPopUp();
11327     }
11328     gameMode = AnalyzeFile;
11329     pausing = FALSE;
11330     ModeHighlight();
11331     SetGameInfo();
11332
11333     StartAnalysisClock();
11334     GetTimeMark(&lastNodeCountTime);
11335     lastNodeCount = 0;
11336 }
11337
11338 void
11339 MachineWhiteEvent()
11340 {
11341     char buf[MSG_SIZ];
11342     char *bookHit = NULL;
11343
11344     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11345       return;
11346
11347
11348     if (gameMode == PlayFromGameFile || 
11349         gameMode == TwoMachinesPlay  || 
11350         gameMode == Training         || 
11351         gameMode == AnalyzeMode      || 
11352         gameMode == EndOfGame)
11353         EditGameEvent();
11354
11355     if (gameMode == EditPosition) 
11356         EditPositionDone(TRUE);
11357
11358     if (!WhiteOnMove(currentMove)) {
11359         DisplayError(_("It is not White's turn"), 0);
11360         return;
11361     }
11362   
11363     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11364       ExitAnalyzeMode();
11365
11366     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11367         gameMode == AnalyzeFile)
11368         TruncateGame();
11369
11370     ResurrectChessProgram();    /* in case it isn't running */
11371     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11372         gameMode = MachinePlaysWhite;
11373         ResetClocks();
11374     } else
11375     gameMode = MachinePlaysWhite;
11376     pausing = FALSE;
11377     ModeHighlight();
11378     SetGameInfo();
11379     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11380     DisplayTitle(buf);
11381     if (first.sendName) {
11382       sprintf(buf, "name %s\n", gameInfo.black);
11383       SendToProgram(buf, &first);
11384     }
11385     if (first.sendTime) {
11386       if (first.useColors) {
11387         SendToProgram("black\n", &first); /*gnu kludge*/
11388       }
11389       SendTimeRemaining(&first, TRUE);
11390     }
11391     if (first.useColors) {
11392       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11393     }
11394     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11395     SetMachineThinkingEnables();
11396     first.maybeThinking = TRUE;
11397     StartClocks();
11398     firstMove = FALSE;
11399
11400     if (appData.autoFlipView && !flipView) {
11401       flipView = !flipView;
11402       DrawPosition(FALSE, NULL);
11403       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11404     }
11405
11406     if(bookHit) { // [HGM] book: simulate book reply
11407         static char bookMove[MSG_SIZ]; // a bit generous?
11408
11409         programStats.nodes = programStats.depth = programStats.time = 
11410         programStats.score = programStats.got_only_move = 0;
11411         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11412
11413         strcpy(bookMove, "move ");
11414         strcat(bookMove, bookHit);
11415         HandleMachineMove(bookMove, &first);
11416     }
11417 }
11418
11419 void
11420 MachineBlackEvent()
11421 {
11422     char buf[MSG_SIZ];
11423    char *bookHit = NULL;
11424
11425     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11426         return;
11427
11428
11429     if (gameMode == PlayFromGameFile || 
11430         gameMode == TwoMachinesPlay  || 
11431         gameMode == Training         || 
11432         gameMode == AnalyzeMode      || 
11433         gameMode == EndOfGame)
11434         EditGameEvent();
11435
11436     if (gameMode == EditPosition) 
11437         EditPositionDone(TRUE);
11438
11439     if (WhiteOnMove(currentMove)) {
11440         DisplayError(_("It is not Black's turn"), 0);
11441         return;
11442     }
11443     
11444     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11445       ExitAnalyzeMode();
11446
11447     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11448         gameMode == AnalyzeFile)
11449         TruncateGame();
11450
11451     ResurrectChessProgram();    /* in case it isn't running */
11452     gameMode = MachinePlaysBlack;
11453     pausing = FALSE;
11454     ModeHighlight();
11455     SetGameInfo();
11456     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11457     DisplayTitle(buf);
11458     if (first.sendName) {
11459       sprintf(buf, "name %s\n", gameInfo.white);
11460       SendToProgram(buf, &first);
11461     }
11462     if (first.sendTime) {
11463       if (first.useColors) {
11464         SendToProgram("white\n", &first); /*gnu kludge*/
11465       }
11466       SendTimeRemaining(&first, FALSE);
11467     }
11468     if (first.useColors) {
11469       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11470     }
11471     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11472     SetMachineThinkingEnables();
11473     first.maybeThinking = TRUE;
11474     StartClocks();
11475
11476     if (appData.autoFlipView && flipView) {
11477       flipView = !flipView;
11478       DrawPosition(FALSE, NULL);
11479       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11480     }
11481     if(bookHit) { // [HGM] book: simulate book reply
11482         static char bookMove[MSG_SIZ]; // a bit generous?
11483
11484         programStats.nodes = programStats.depth = programStats.time = 
11485         programStats.score = programStats.got_only_move = 0;
11486         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11487
11488         strcpy(bookMove, "move ");
11489         strcat(bookMove, bookHit);
11490         HandleMachineMove(bookMove, &first);
11491     }
11492 }
11493
11494
11495 void
11496 DisplayTwoMachinesTitle()
11497 {
11498     char buf[MSG_SIZ];
11499     if (appData.matchGames > 0) {
11500         if (first.twoMachinesColor[0] == 'w') {
11501             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11502                     gameInfo.white, gameInfo.black,
11503                     first.matchWins, second.matchWins,
11504                     matchGame - 1 - (first.matchWins + second.matchWins));
11505         } else {
11506             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11507                     gameInfo.white, gameInfo.black,
11508                     second.matchWins, first.matchWins,
11509                     matchGame - 1 - (first.matchWins + second.matchWins));
11510         }
11511     } else {
11512         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11513     }
11514     DisplayTitle(buf);
11515 }
11516
11517 void
11518 TwoMachinesEvent P((void))
11519 {
11520     int i;
11521     char buf[MSG_SIZ];
11522     ChessProgramState *onmove;
11523     char *bookHit = NULL;
11524     
11525     if (appData.noChessProgram) return;
11526
11527     switch (gameMode) {
11528       case TwoMachinesPlay:
11529         return;
11530       case MachinePlaysWhite:
11531       case MachinePlaysBlack:
11532         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11533             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11534             return;
11535         }
11536         /* fall through */
11537       case BeginningOfGame:
11538       case PlayFromGameFile:
11539       case EndOfGame:
11540         EditGameEvent();
11541         if (gameMode != EditGame) return;
11542         break;
11543       case EditPosition:
11544         EditPositionDone(TRUE);
11545         break;
11546       case AnalyzeMode:
11547       case AnalyzeFile:
11548         ExitAnalyzeMode();
11549         break;
11550       case EditGame:
11551       default:
11552         break;
11553     }
11554
11555 //    forwardMostMove = currentMove;
11556     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11557     ResurrectChessProgram();    /* in case first program isn't running */
11558
11559     if (second.pr == NULL) {
11560         StartChessProgram(&second);
11561         if (second.protocolVersion == 1) {
11562           TwoMachinesEventIfReady();
11563         } else {
11564           /* kludge: allow timeout for initial "feature" command */
11565           FreezeUI();
11566           DisplayMessage("", _("Starting second chess program"));
11567           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11568         }
11569         return;
11570     }
11571     DisplayMessage("", "");
11572     InitChessProgram(&second, FALSE);
11573     SendToProgram("force\n", &second);
11574     if (startedFromSetupPosition) {
11575         SendBoard(&second, backwardMostMove);
11576     if (appData.debugMode) {
11577         fprintf(debugFP, "Two Machines\n");
11578     }
11579     }
11580     for (i = backwardMostMove; i < forwardMostMove; i++) {
11581         SendMoveToProgram(i, &second);
11582     }
11583
11584     gameMode = TwoMachinesPlay;
11585     pausing = FALSE;
11586     ModeHighlight();
11587     SetGameInfo();
11588     DisplayTwoMachinesTitle();
11589     firstMove = TRUE;
11590     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11591         onmove = &first;
11592     } else {
11593         onmove = &second;
11594     }
11595
11596     SendToProgram(first.computerString, &first);
11597     if (first.sendName) {
11598       sprintf(buf, "name %s\n", second.tidy);
11599       SendToProgram(buf, &first);
11600     }
11601     SendToProgram(second.computerString, &second);
11602     if (second.sendName) {
11603       sprintf(buf, "name %s\n", first.tidy);
11604       SendToProgram(buf, &second);
11605     }
11606
11607     ResetClocks();
11608     if (!first.sendTime || !second.sendTime) {
11609         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11610         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11611     }
11612     if (onmove->sendTime) {
11613       if (onmove->useColors) {
11614         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11615       }
11616       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11617     }
11618     if (onmove->useColors) {
11619       SendToProgram(onmove->twoMachinesColor, onmove);
11620     }
11621     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11622 //    SendToProgram("go\n", onmove);
11623     onmove->maybeThinking = TRUE;
11624     SetMachineThinkingEnables();
11625
11626     StartClocks();
11627
11628     if(bookHit) { // [HGM] book: simulate book reply
11629         static char bookMove[MSG_SIZ]; // a bit generous?
11630
11631         programStats.nodes = programStats.depth = programStats.time = 
11632         programStats.score = programStats.got_only_move = 0;
11633         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11634
11635         strcpy(bookMove, "move ");
11636         strcat(bookMove, bookHit);
11637         savedMessage = bookMove; // args for deferred call
11638         savedState = onmove;
11639         ScheduleDelayedEvent(DeferredBookMove, 1);
11640     }
11641 }
11642
11643 void
11644 TrainingEvent()
11645 {
11646     if (gameMode == Training) {
11647       SetTrainingModeOff();
11648       gameMode = PlayFromGameFile;
11649       DisplayMessage("", _("Training mode off"));
11650     } else {
11651       gameMode = Training;
11652       animateTraining = appData.animate;
11653
11654       /* make sure we are not already at the end of the game */
11655       if (currentMove < forwardMostMove) {
11656         SetTrainingModeOn();
11657         DisplayMessage("", _("Training mode on"));
11658       } else {
11659         gameMode = PlayFromGameFile;
11660         DisplayError(_("Already at end of game"), 0);
11661       }
11662     }
11663     ModeHighlight();
11664 }
11665
11666 void
11667 IcsClientEvent()
11668 {
11669     if (!appData.icsActive) return;
11670     switch (gameMode) {
11671       case IcsPlayingWhite:
11672       case IcsPlayingBlack:
11673       case IcsObserving:
11674       case IcsIdle:
11675       case BeginningOfGame:
11676       case IcsExamining:
11677         return;
11678
11679       case EditGame:
11680         break;
11681
11682       case EditPosition:
11683         EditPositionDone(TRUE);
11684         break;
11685
11686       case AnalyzeMode:
11687       case AnalyzeFile:
11688         ExitAnalyzeMode();
11689         break;
11690         
11691       default:
11692         EditGameEvent();
11693         break;
11694     }
11695
11696     gameMode = IcsIdle;
11697     ModeHighlight();
11698     return;
11699 }
11700
11701
11702 void
11703 EditGameEvent()
11704 {
11705     int i;
11706
11707     switch (gameMode) {
11708       case Training:
11709         SetTrainingModeOff();
11710         break;
11711       case MachinePlaysWhite:
11712       case MachinePlaysBlack:
11713       case BeginningOfGame:
11714         SendToProgram("force\n", &first);
11715         SetUserThinkingEnables();
11716         break;
11717       case PlayFromGameFile:
11718         (void) StopLoadGameTimer();
11719         if (gameFileFP != NULL) {
11720             gameFileFP = NULL;
11721         }
11722         break;
11723       case EditPosition:
11724         EditPositionDone(TRUE);
11725         break;
11726       case AnalyzeMode:
11727       case AnalyzeFile:
11728         ExitAnalyzeMode();
11729         SendToProgram("force\n", &first);
11730         break;
11731       case TwoMachinesPlay:
11732         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11733         ResurrectChessProgram();
11734         SetUserThinkingEnables();
11735         break;
11736       case EndOfGame:
11737         ResurrectChessProgram();
11738         break;
11739       case IcsPlayingBlack:
11740       case IcsPlayingWhite:
11741         DisplayError(_("Warning: You are still playing a game"), 0);
11742         break;
11743       case IcsObserving:
11744         DisplayError(_("Warning: You are still observing a game"), 0);
11745         break;
11746       case IcsExamining:
11747         DisplayError(_("Warning: You are still examining a game"), 0);
11748         break;
11749       case IcsIdle:
11750         break;
11751       case EditGame:
11752       default:
11753         return;
11754     }
11755     
11756     pausing = FALSE;
11757     StopClocks();
11758     first.offeredDraw = second.offeredDraw = 0;
11759
11760     if (gameMode == PlayFromGameFile) {
11761         whiteTimeRemaining = timeRemaining[0][currentMove];
11762         blackTimeRemaining = timeRemaining[1][currentMove];
11763         DisplayTitle("");
11764     }
11765
11766     if (gameMode == MachinePlaysWhite ||
11767         gameMode == MachinePlaysBlack ||
11768         gameMode == TwoMachinesPlay ||
11769         gameMode == EndOfGame) {
11770         i = forwardMostMove;
11771         while (i > currentMove) {
11772             SendToProgram("undo\n", &first);
11773             i--;
11774         }
11775         whiteTimeRemaining = timeRemaining[0][currentMove];
11776         blackTimeRemaining = timeRemaining[1][currentMove];
11777         DisplayBothClocks();
11778         if (whiteFlag || blackFlag) {
11779             whiteFlag = blackFlag = 0;
11780         }
11781         DisplayTitle("");
11782     }           
11783     
11784     gameMode = EditGame;
11785     ModeHighlight();
11786     SetGameInfo();
11787 }
11788
11789
11790 void
11791 EditPositionEvent()
11792 {
11793     if (gameMode == EditPosition) {
11794         EditGameEvent();
11795         return;
11796     }
11797     
11798     EditGameEvent();
11799     if (gameMode != EditGame) return;
11800     
11801     gameMode = EditPosition;
11802     ModeHighlight();
11803     SetGameInfo();
11804     if (currentMove > 0)
11805       CopyBoard(boards[0], boards[currentMove]);
11806     
11807     blackPlaysFirst = !WhiteOnMove(currentMove);
11808     ResetClocks();
11809     currentMove = forwardMostMove = backwardMostMove = 0;
11810     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11811     DisplayMove(-1);
11812 }
11813
11814 void
11815 ExitAnalyzeMode()
11816 {
11817     /* [DM] icsEngineAnalyze - possible call from other functions */
11818     if (appData.icsEngineAnalyze) {
11819         appData.icsEngineAnalyze = FALSE;
11820
11821         DisplayMessage("",_("Close ICS engine analyze..."));
11822     }
11823     if (first.analysisSupport && first.analyzing) {
11824       SendToProgram("exit\n", &first);
11825       first.analyzing = FALSE;
11826     }
11827     thinkOutput[0] = NULLCHAR;
11828 }
11829
11830 void
11831 EditPositionDone(Boolean fakeRights)
11832 {
11833     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11834
11835     startedFromSetupPosition = TRUE;
11836     InitChessProgram(&first, FALSE);
11837     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11838       boards[0][EP_STATUS] = EP_NONE;
11839       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11840     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11841         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11842         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11843       } else boards[0][CASTLING][2] = NoRights;
11844     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11845         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11846         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11847       } else boards[0][CASTLING][5] = NoRights;
11848     }
11849     SendToProgram("force\n", &first);
11850     if (blackPlaysFirst) {
11851         strcpy(moveList[0], "");
11852         strcpy(parseList[0], "");
11853         currentMove = forwardMostMove = backwardMostMove = 1;
11854         CopyBoard(boards[1], boards[0]);
11855     } else {
11856         currentMove = forwardMostMove = backwardMostMove = 0;
11857     }
11858     SendBoard(&first, forwardMostMove);
11859     if (appData.debugMode) {
11860         fprintf(debugFP, "EditPosDone\n");
11861     }
11862     DisplayTitle("");
11863     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11864     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11865     gameMode = EditGame;
11866     ModeHighlight();
11867     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11868     ClearHighlights(); /* [AS] */
11869 }
11870
11871 /* Pause for `ms' milliseconds */
11872 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11873 void
11874 TimeDelay(ms)
11875      long ms;
11876 {
11877     TimeMark m1, m2;
11878
11879     GetTimeMark(&m1);
11880     do {
11881         GetTimeMark(&m2);
11882     } while (SubtractTimeMarks(&m2, &m1) < ms);
11883 }
11884
11885 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11886 void
11887 SendMultiLineToICS(buf)
11888      char *buf;
11889 {
11890     char temp[MSG_SIZ+1], *p;
11891     int len;
11892
11893     len = strlen(buf);
11894     if (len > MSG_SIZ)
11895       len = MSG_SIZ;
11896   
11897     strncpy(temp, buf, len);
11898     temp[len] = 0;
11899
11900     p = temp;
11901     while (*p) {
11902         if (*p == '\n' || *p == '\r')
11903           *p = ' ';
11904         ++p;
11905     }
11906
11907     strcat(temp, "\n");
11908     SendToICS(temp);
11909     SendToPlayer(temp, strlen(temp));
11910 }
11911
11912 void
11913 SetWhiteToPlayEvent()
11914 {
11915     if (gameMode == EditPosition) {
11916         blackPlaysFirst = FALSE;
11917         DisplayBothClocks();    /* works because currentMove is 0 */
11918     } else if (gameMode == IcsExamining) {
11919         SendToICS(ics_prefix);
11920         SendToICS("tomove white\n");
11921     }
11922 }
11923
11924 void
11925 SetBlackToPlayEvent()
11926 {
11927     if (gameMode == EditPosition) {
11928         blackPlaysFirst = TRUE;
11929         currentMove = 1;        /* kludge */
11930         DisplayBothClocks();
11931         currentMove = 0;
11932     } else if (gameMode == IcsExamining) {
11933         SendToICS(ics_prefix);
11934         SendToICS("tomove black\n");
11935     }
11936 }
11937
11938 void
11939 EditPositionMenuEvent(selection, x, y)
11940      ChessSquare selection;
11941      int x, y;
11942 {
11943     char buf[MSG_SIZ];
11944     ChessSquare piece = boards[0][y][x];
11945
11946     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11947
11948     switch (selection) {
11949       case ClearBoard:
11950         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11951             SendToICS(ics_prefix);
11952             SendToICS("bsetup clear\n");
11953         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11954             SendToICS(ics_prefix);
11955             SendToICS("clearboard\n");
11956         } else {
11957             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11958                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11959                 for (y = 0; y < BOARD_HEIGHT; y++) {
11960                     if (gameMode == IcsExamining) {
11961                         if (boards[currentMove][y][x] != EmptySquare) {
11962                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11963                                     AAA + x, ONE + y);
11964                             SendToICS(buf);
11965                         }
11966                     } else {
11967                         boards[0][y][x] = p;
11968                     }
11969                 }
11970             }
11971         }
11972         if (gameMode == EditPosition) {
11973             DrawPosition(FALSE, boards[0]);
11974         }
11975         break;
11976
11977       case WhitePlay:
11978         SetWhiteToPlayEvent();
11979         break;
11980
11981       case BlackPlay:
11982         SetBlackToPlayEvent();
11983         break;
11984
11985       case EmptySquare:
11986         if (gameMode == IcsExamining) {
11987             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11988             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11989             SendToICS(buf);
11990         } else {
11991             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11992                 if(x == BOARD_LEFT-2) {
11993                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11994                     boards[0][y][1] = 0;
11995                 } else
11996                 if(x == BOARD_RGHT+1) {
11997                     if(y >= gameInfo.holdingsSize) break;
11998                     boards[0][y][BOARD_WIDTH-2] = 0;
11999                 } else break;
12000             }
12001             boards[0][y][x] = EmptySquare;
12002             DrawPosition(FALSE, boards[0]);
12003         }
12004         break;
12005
12006       case PromotePiece:
12007         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12008            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12009             selection = (ChessSquare) (PROMOTED piece);
12010         } else if(piece == EmptySquare) selection = WhiteSilver;
12011         else selection = (ChessSquare)((int)piece - 1);
12012         goto defaultlabel;
12013
12014       case DemotePiece:
12015         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12016            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12017             selection = (ChessSquare) (DEMOTED piece);
12018         } else if(piece == EmptySquare) selection = BlackSilver;
12019         else selection = (ChessSquare)((int)piece + 1);       
12020         goto defaultlabel;
12021
12022       case WhiteQueen:
12023       case BlackQueen:
12024         if(gameInfo.variant == VariantShatranj ||
12025            gameInfo.variant == VariantXiangqi  ||
12026            gameInfo.variant == VariantCourier  ||
12027            gameInfo.variant == VariantMakruk     )
12028             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12029         goto defaultlabel;
12030
12031       case WhiteKing:
12032       case BlackKing:
12033         if(gameInfo.variant == VariantXiangqi)
12034             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12035         if(gameInfo.variant == VariantKnightmate)
12036             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12037       default:
12038         defaultlabel:
12039         if (gameMode == IcsExamining) {
12040             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12041             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12042                     PieceToChar(selection), AAA + x, ONE + y);
12043             SendToICS(buf);
12044         } else {
12045             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12046                 int n;
12047                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12048                     n = PieceToNumber(selection - BlackPawn);
12049                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12050                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12051                     boards[0][BOARD_HEIGHT-1-n][1]++;
12052                 } else
12053                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12054                     n = PieceToNumber(selection);
12055                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12056                     boards[0][n][BOARD_WIDTH-1] = selection;
12057                     boards[0][n][BOARD_WIDTH-2]++;
12058                 }
12059             } else
12060             boards[0][y][x] = selection;
12061             DrawPosition(TRUE, boards[0]);
12062         }
12063         break;
12064     }
12065 }
12066
12067
12068 void
12069 DropMenuEvent(selection, x, y)
12070      ChessSquare selection;
12071      int x, y;
12072 {
12073     ChessMove moveType;
12074
12075     switch (gameMode) {
12076       case IcsPlayingWhite:
12077       case MachinePlaysBlack:
12078         if (!WhiteOnMove(currentMove)) {
12079             DisplayMoveError(_("It is Black's turn"));
12080             return;
12081         }
12082         moveType = WhiteDrop;
12083         break;
12084       case IcsPlayingBlack:
12085       case MachinePlaysWhite:
12086         if (WhiteOnMove(currentMove)) {
12087             DisplayMoveError(_("It is White's turn"));
12088             return;
12089         }
12090         moveType = BlackDrop;
12091         break;
12092       case EditGame:
12093         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12094         break;
12095       default:
12096         return;
12097     }
12098
12099     if (moveType == BlackDrop && selection < BlackPawn) {
12100       selection = (ChessSquare) ((int) selection
12101                                  + (int) BlackPawn - (int) WhitePawn);
12102     }
12103     if (boards[currentMove][y][x] != EmptySquare) {
12104         DisplayMoveError(_("That square is occupied"));
12105         return;
12106     }
12107
12108     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12109 }
12110
12111 void
12112 AcceptEvent()
12113 {
12114     /* Accept a pending offer of any kind from opponent */
12115     
12116     if (appData.icsActive) {
12117         SendToICS(ics_prefix);
12118         SendToICS("accept\n");
12119     } else if (cmailMsgLoaded) {
12120         if (currentMove == cmailOldMove &&
12121             commentList[cmailOldMove] != NULL &&
12122             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12123                    "Black offers a draw" : "White offers a draw")) {
12124             TruncateGame();
12125             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12126             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12127         } else {
12128             DisplayError(_("There is no pending offer on this move"), 0);
12129             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12130         }
12131     } else {
12132         /* Not used for offers from chess program */
12133     }
12134 }
12135
12136 void
12137 DeclineEvent()
12138 {
12139     /* Decline a pending offer of any kind from opponent */
12140     
12141     if (appData.icsActive) {
12142         SendToICS(ics_prefix);
12143         SendToICS("decline\n");
12144     } else if (cmailMsgLoaded) {
12145         if (currentMove == cmailOldMove &&
12146             commentList[cmailOldMove] != NULL &&
12147             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12148                    "Black offers a draw" : "White offers a draw")) {
12149 #ifdef NOTDEF
12150             AppendComment(cmailOldMove, "Draw declined", TRUE);
12151             DisplayComment(cmailOldMove - 1, "Draw declined");
12152 #endif /*NOTDEF*/
12153         } else {
12154             DisplayError(_("There is no pending offer on this move"), 0);
12155         }
12156     } else {
12157         /* Not used for offers from chess program */
12158     }
12159 }
12160
12161 void
12162 RematchEvent()
12163 {
12164     /* Issue ICS rematch command */
12165     if (appData.icsActive) {
12166         SendToICS(ics_prefix);
12167         SendToICS("rematch\n");
12168     }
12169 }
12170
12171 void
12172 CallFlagEvent()
12173 {
12174     /* Call your opponent's flag (claim a win on time) */
12175     if (appData.icsActive) {
12176         SendToICS(ics_prefix);
12177         SendToICS("flag\n");
12178     } else {
12179         switch (gameMode) {
12180           default:
12181             return;
12182           case MachinePlaysWhite:
12183             if (whiteFlag) {
12184                 if (blackFlag)
12185                   GameEnds(GameIsDrawn, "Both players ran out of time",
12186                            GE_PLAYER);
12187                 else
12188                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12189             } else {
12190                 DisplayError(_("Your opponent is not out of time"), 0);
12191             }
12192             break;
12193           case MachinePlaysBlack:
12194             if (blackFlag) {
12195                 if (whiteFlag)
12196                   GameEnds(GameIsDrawn, "Both players ran out of time",
12197                            GE_PLAYER);
12198                 else
12199                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12200             } else {
12201                 DisplayError(_("Your opponent is not out of time"), 0);
12202             }
12203             break;
12204         }
12205     }
12206 }
12207
12208 void
12209 DrawEvent()
12210 {
12211     /* Offer draw or accept pending draw offer from opponent */
12212     
12213     if (appData.icsActive) {
12214         /* Note: tournament rules require draw offers to be
12215            made after you make your move but before you punch
12216            your clock.  Currently ICS doesn't let you do that;
12217            instead, you immediately punch your clock after making
12218            a move, but you can offer a draw at any time. */
12219         
12220         SendToICS(ics_prefix);
12221         SendToICS("draw\n");
12222         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12223     } else if (cmailMsgLoaded) {
12224         if (currentMove == cmailOldMove &&
12225             commentList[cmailOldMove] != NULL &&
12226             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12227                    "Black offers a draw" : "White offers a draw")) {
12228             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12229             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12230         } else if (currentMove == cmailOldMove + 1) {
12231             char *offer = WhiteOnMove(cmailOldMove) ?
12232               "White offers a draw" : "Black offers a draw";
12233             AppendComment(currentMove, offer, TRUE);
12234             DisplayComment(currentMove - 1, offer);
12235             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12236         } else {
12237             DisplayError(_("You must make your move before offering a draw"), 0);
12238             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12239         }
12240     } else if (first.offeredDraw) {
12241         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12242     } else {
12243         if (first.sendDrawOffers) {
12244             SendToProgram("draw\n", &first);
12245             userOfferedDraw = TRUE;
12246         }
12247     }
12248 }
12249
12250 void
12251 AdjournEvent()
12252 {
12253     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12254     
12255     if (appData.icsActive) {
12256         SendToICS(ics_prefix);
12257         SendToICS("adjourn\n");
12258     } else {
12259         /* Currently GNU Chess doesn't offer or accept Adjourns */
12260     }
12261 }
12262
12263
12264 void
12265 AbortEvent()
12266 {
12267     /* Offer Abort or accept pending Abort offer from opponent */
12268     
12269     if (appData.icsActive) {
12270         SendToICS(ics_prefix);
12271         SendToICS("abort\n");
12272     } else {
12273         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12274     }
12275 }
12276
12277 void
12278 ResignEvent()
12279 {
12280     /* Resign.  You can do this even if it's not your turn. */
12281     
12282     if (appData.icsActive) {
12283         SendToICS(ics_prefix);
12284         SendToICS("resign\n");
12285     } else {
12286         switch (gameMode) {
12287           case MachinePlaysWhite:
12288             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12289             break;
12290           case MachinePlaysBlack:
12291             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12292             break;
12293           case EditGame:
12294             if (cmailMsgLoaded) {
12295                 TruncateGame();
12296                 if (WhiteOnMove(cmailOldMove)) {
12297                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12298                 } else {
12299                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12300                 }
12301                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12302             }
12303             break;
12304           default:
12305             break;
12306         }
12307     }
12308 }
12309
12310
12311 void
12312 StopObservingEvent()
12313 {
12314     /* Stop observing current games */
12315     SendToICS(ics_prefix);
12316     SendToICS("unobserve\n");
12317 }
12318
12319 void
12320 StopExaminingEvent()
12321 {
12322     /* Stop observing current game */
12323     SendToICS(ics_prefix);
12324     SendToICS("unexamine\n");
12325 }
12326
12327 void
12328 ForwardInner(target)
12329      int target;
12330 {
12331     int limit;
12332
12333     if (appData.debugMode)
12334         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12335                 target, currentMove, forwardMostMove);
12336
12337     if (gameMode == EditPosition)
12338       return;
12339
12340     if (gameMode == PlayFromGameFile && !pausing)
12341       PauseEvent();
12342     
12343     if (gameMode == IcsExamining && pausing)
12344       limit = pauseExamForwardMostMove;
12345     else
12346       limit = forwardMostMove;
12347     
12348     if (target > limit) target = limit;
12349
12350     if (target > 0 && moveList[target - 1][0]) {
12351         int fromX, fromY, toX, toY;
12352         toX = moveList[target - 1][2] - AAA;
12353         toY = moveList[target - 1][3] - ONE;
12354         if (moveList[target - 1][1] == '@') {
12355             if (appData.highlightLastMove) {
12356                 SetHighlights(-1, -1, toX, toY);
12357             }
12358         } else {
12359             fromX = moveList[target - 1][0] - AAA;
12360             fromY = moveList[target - 1][1] - ONE;
12361             if (target == currentMove + 1) {
12362                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12363             }
12364             if (appData.highlightLastMove) {
12365                 SetHighlights(fromX, fromY, toX, toY);
12366             }
12367         }
12368     }
12369     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12370         gameMode == Training || gameMode == PlayFromGameFile || 
12371         gameMode == AnalyzeFile) {
12372         while (currentMove < target) {
12373             SendMoveToProgram(currentMove++, &first);
12374         }
12375     } else {
12376         currentMove = target;
12377     }
12378     
12379     if (gameMode == EditGame || gameMode == EndOfGame) {
12380         whiteTimeRemaining = timeRemaining[0][currentMove];
12381         blackTimeRemaining = timeRemaining[1][currentMove];
12382     }
12383     DisplayBothClocks();
12384     DisplayMove(currentMove - 1);
12385     DrawPosition(FALSE, boards[currentMove]);
12386     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12387     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12388         DisplayComment(currentMove - 1, commentList[currentMove]);
12389     }
12390 }
12391
12392
12393 void
12394 ForwardEvent()
12395 {
12396     if (gameMode == IcsExamining && !pausing) {
12397         SendToICS(ics_prefix);
12398         SendToICS("forward\n");
12399     } else {
12400         ForwardInner(currentMove + 1);
12401     }
12402 }
12403
12404 void
12405 ToEndEvent()
12406 {
12407     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12408         /* to optimze, we temporarily turn off analysis mode while we feed
12409          * the remaining moves to the engine. Otherwise we get analysis output
12410          * after each move.
12411          */ 
12412         if (first.analysisSupport) {
12413           SendToProgram("exit\nforce\n", &first);
12414           first.analyzing = FALSE;
12415         }
12416     }
12417         
12418     if (gameMode == IcsExamining && !pausing) {
12419         SendToICS(ics_prefix);
12420         SendToICS("forward 999999\n");
12421     } else {
12422         ForwardInner(forwardMostMove);
12423     }
12424
12425     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12426         /* we have fed all the moves, so reactivate analysis mode */
12427         SendToProgram("analyze\n", &first);
12428         first.analyzing = TRUE;
12429         /*first.maybeThinking = TRUE;*/
12430         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12431     }
12432 }
12433
12434 void
12435 BackwardInner(target)
12436      int target;
12437 {
12438     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12439
12440     if (appData.debugMode)
12441         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12442                 target, currentMove, forwardMostMove);
12443
12444     if (gameMode == EditPosition) return;
12445     if (currentMove <= backwardMostMove) {
12446         ClearHighlights();
12447         DrawPosition(full_redraw, boards[currentMove]);
12448         return;
12449     }
12450     if (gameMode == PlayFromGameFile && !pausing)
12451       PauseEvent();
12452     
12453     if (moveList[target][0]) {
12454         int fromX, fromY, toX, toY;
12455         toX = moveList[target][2] - AAA;
12456         toY = moveList[target][3] - ONE;
12457         if (moveList[target][1] == '@') {
12458             if (appData.highlightLastMove) {
12459                 SetHighlights(-1, -1, toX, toY);
12460             }
12461         } else {
12462             fromX = moveList[target][0] - AAA;
12463             fromY = moveList[target][1] - ONE;
12464             if (target == currentMove - 1) {
12465                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12466             }
12467             if (appData.highlightLastMove) {
12468                 SetHighlights(fromX, fromY, toX, toY);
12469             }
12470         }
12471     }
12472     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12473         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12474         while (currentMove > target) {
12475             SendToProgram("undo\n", &first);
12476             currentMove--;
12477         }
12478     } else {
12479         currentMove = target;
12480     }
12481     
12482     if (gameMode == EditGame || gameMode == EndOfGame) {
12483         whiteTimeRemaining = timeRemaining[0][currentMove];
12484         blackTimeRemaining = timeRemaining[1][currentMove];
12485     }
12486     DisplayBothClocks();
12487     DisplayMove(currentMove - 1);
12488     DrawPosition(full_redraw, boards[currentMove]);
12489     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12490     // [HGM] PV info: routine tests if comment empty
12491     DisplayComment(currentMove - 1, commentList[currentMove]);
12492 }
12493
12494 void
12495 BackwardEvent()
12496 {
12497     if (gameMode == IcsExamining && !pausing) {
12498         SendToICS(ics_prefix);
12499         SendToICS("backward\n");
12500     } else {
12501         BackwardInner(currentMove - 1);
12502     }
12503 }
12504
12505 void
12506 ToStartEvent()
12507 {
12508     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12509         /* to optimize, we temporarily turn off analysis mode while we undo
12510          * all the moves. Otherwise we get analysis output after each undo.
12511          */ 
12512         if (first.analysisSupport) {
12513           SendToProgram("exit\nforce\n", &first);
12514           first.analyzing = FALSE;
12515         }
12516     }
12517
12518     if (gameMode == IcsExamining && !pausing) {
12519         SendToICS(ics_prefix);
12520         SendToICS("backward 999999\n");
12521     } else {
12522         BackwardInner(backwardMostMove);
12523     }
12524
12525     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12526         /* we have fed all the moves, so reactivate analysis mode */
12527         SendToProgram("analyze\n", &first);
12528         first.analyzing = TRUE;
12529         /*first.maybeThinking = TRUE;*/
12530         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12531     }
12532 }
12533
12534 void
12535 ToNrEvent(int to)
12536 {
12537   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12538   if (to >= forwardMostMove) to = forwardMostMove;
12539   if (to <= backwardMostMove) to = backwardMostMove;
12540   if (to < currentMove) {
12541     BackwardInner(to);
12542   } else {
12543     ForwardInner(to);
12544   }
12545 }
12546
12547 void
12548 RevertEvent()
12549 {
12550     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12551         return;
12552     }
12553     if (gameMode != IcsExamining) {
12554         DisplayError(_("You are not examining a game"), 0);
12555         return;
12556     }
12557     if (pausing) {
12558         DisplayError(_("You can't revert while pausing"), 0);
12559         return;
12560     }
12561     SendToICS(ics_prefix);
12562     SendToICS("revert\n");
12563 }
12564
12565 void
12566 RetractMoveEvent()
12567 {
12568     switch (gameMode) {
12569       case MachinePlaysWhite:
12570       case MachinePlaysBlack:
12571         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12572             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12573             return;
12574         }
12575         if (forwardMostMove < 2) return;
12576         currentMove = forwardMostMove = forwardMostMove - 2;
12577         whiteTimeRemaining = timeRemaining[0][currentMove];
12578         blackTimeRemaining = timeRemaining[1][currentMove];
12579         DisplayBothClocks();
12580         DisplayMove(currentMove - 1);
12581         ClearHighlights();/*!! could figure this out*/
12582         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12583         SendToProgram("remove\n", &first);
12584         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12585         break;
12586
12587       case BeginningOfGame:
12588       default:
12589         break;
12590
12591       case IcsPlayingWhite:
12592       case IcsPlayingBlack:
12593         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12594             SendToICS(ics_prefix);
12595             SendToICS("takeback 2\n");
12596         } else {
12597             SendToICS(ics_prefix);
12598             SendToICS("takeback 1\n");
12599         }
12600         break;
12601     }
12602 }
12603
12604 void
12605 MoveNowEvent()
12606 {
12607     ChessProgramState *cps;
12608
12609     switch (gameMode) {
12610       case MachinePlaysWhite:
12611         if (!WhiteOnMove(forwardMostMove)) {
12612             DisplayError(_("It is your turn"), 0);
12613             return;
12614         }
12615         cps = &first;
12616         break;
12617       case MachinePlaysBlack:
12618         if (WhiteOnMove(forwardMostMove)) {
12619             DisplayError(_("It is your turn"), 0);
12620             return;
12621         }
12622         cps = &first;
12623         break;
12624       case TwoMachinesPlay:
12625         if (WhiteOnMove(forwardMostMove) ==
12626             (first.twoMachinesColor[0] == 'w')) {
12627             cps = &first;
12628         } else {
12629             cps = &second;
12630         }
12631         break;
12632       case BeginningOfGame:
12633       default:
12634         return;
12635     }
12636     SendToProgram("?\n", cps);
12637 }
12638
12639 void
12640 TruncateGameEvent()
12641 {
12642     EditGameEvent();
12643     if (gameMode != EditGame) return;
12644     TruncateGame();
12645 }
12646
12647 void
12648 TruncateGame()
12649 {
12650     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12651     if (forwardMostMove > currentMove) {
12652         if (gameInfo.resultDetails != NULL) {
12653             free(gameInfo.resultDetails);
12654             gameInfo.resultDetails = NULL;
12655             gameInfo.result = GameUnfinished;
12656         }
12657         forwardMostMove = currentMove;
12658         HistorySet(parseList, backwardMostMove, forwardMostMove,
12659                    currentMove-1);
12660     }
12661 }
12662
12663 void
12664 HintEvent()
12665 {
12666     if (appData.noChessProgram) return;
12667     switch (gameMode) {
12668       case MachinePlaysWhite:
12669         if (WhiteOnMove(forwardMostMove)) {
12670             DisplayError(_("Wait until your turn"), 0);
12671             return;
12672         }
12673         break;
12674       case BeginningOfGame:
12675       case MachinePlaysBlack:
12676         if (!WhiteOnMove(forwardMostMove)) {
12677             DisplayError(_("Wait until your turn"), 0);
12678             return;
12679         }
12680         break;
12681       default:
12682         DisplayError(_("No hint available"), 0);
12683         return;
12684     }
12685     SendToProgram("hint\n", &first);
12686     hintRequested = TRUE;
12687 }
12688
12689 void
12690 BookEvent()
12691 {
12692     if (appData.noChessProgram) return;
12693     switch (gameMode) {
12694       case MachinePlaysWhite:
12695         if (WhiteOnMove(forwardMostMove)) {
12696             DisplayError(_("Wait until your turn"), 0);
12697             return;
12698         }
12699         break;
12700       case BeginningOfGame:
12701       case MachinePlaysBlack:
12702         if (!WhiteOnMove(forwardMostMove)) {
12703             DisplayError(_("Wait until your turn"), 0);
12704             return;
12705         }
12706         break;
12707       case EditPosition:
12708         EditPositionDone(TRUE);
12709         break;
12710       case TwoMachinesPlay:
12711         return;
12712       default:
12713         break;
12714     }
12715     SendToProgram("bk\n", &first);
12716     bookOutput[0] = NULLCHAR;
12717     bookRequested = TRUE;
12718 }
12719
12720 void
12721 AboutGameEvent()
12722 {
12723     char *tags = PGNTags(&gameInfo);
12724     TagsPopUp(tags, CmailMsg());
12725     free(tags);
12726 }
12727
12728 /* end button procedures */
12729
12730 void
12731 PrintPosition(fp, move)
12732      FILE *fp;
12733      int move;
12734 {
12735     int i, j;
12736     
12737     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12738         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12739             char c = PieceToChar(boards[move][i][j]);
12740             fputc(c == 'x' ? '.' : c, fp);
12741             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12742         }
12743     }
12744     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12745       fprintf(fp, "white to play\n");
12746     else
12747       fprintf(fp, "black to play\n");
12748 }
12749
12750 void
12751 PrintOpponents(fp)
12752      FILE *fp;
12753 {
12754     if (gameInfo.white != NULL) {
12755         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12756     } else {
12757         fprintf(fp, "\n");
12758     }
12759 }
12760
12761 /* Find last component of program's own name, using some heuristics */
12762 void
12763 TidyProgramName(prog, host, buf)
12764      char *prog, *host, buf[MSG_SIZ];
12765 {
12766     char *p, *q;
12767     int local = (strcmp(host, "localhost") == 0);
12768     while (!local && (p = strchr(prog, ';')) != NULL) {
12769         p++;
12770         while (*p == ' ') p++;
12771         prog = p;
12772     }
12773     if (*prog == '"' || *prog == '\'') {
12774         q = strchr(prog + 1, *prog);
12775     } else {
12776         q = strchr(prog, ' ');
12777     }
12778     if (q == NULL) q = prog + strlen(prog);
12779     p = q;
12780     while (p >= prog && *p != '/' && *p != '\\') p--;
12781     p++;
12782     if(p == prog && *p == '"') p++;
12783     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12784     memcpy(buf, p, q - p);
12785     buf[q - p] = NULLCHAR;
12786     if (!local) {
12787         strcat(buf, "@");
12788         strcat(buf, host);
12789     }
12790 }
12791
12792 char *
12793 TimeControlTagValue()
12794 {
12795     char buf[MSG_SIZ];
12796     if (!appData.clockMode) {
12797         strcpy(buf, "-");
12798     } else if (movesPerSession > 0) {
12799         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12800     } else if (timeIncrement == 0) {
12801         sprintf(buf, "%ld", timeControl/1000);
12802     } else {
12803         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12804     }
12805     return StrSave(buf);
12806 }
12807
12808 void
12809 SetGameInfo()
12810 {
12811     /* This routine is used only for certain modes */
12812     VariantClass v = gameInfo.variant;
12813     ChessMove r = GameUnfinished;
12814     char *p = NULL;
12815
12816     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12817         r = gameInfo.result; 
12818         p = gameInfo.resultDetails; 
12819         gameInfo.resultDetails = NULL;
12820     }
12821     ClearGameInfo(&gameInfo);
12822     gameInfo.variant = v;
12823
12824     switch (gameMode) {
12825       case MachinePlaysWhite:
12826         gameInfo.event = StrSave( appData.pgnEventHeader );
12827         gameInfo.site = StrSave(HostName());
12828         gameInfo.date = PGNDate();
12829         gameInfo.round = StrSave("-");
12830         gameInfo.white = StrSave(first.tidy);
12831         gameInfo.black = StrSave(UserName());
12832         gameInfo.timeControl = TimeControlTagValue();
12833         break;
12834
12835       case MachinePlaysBlack:
12836         gameInfo.event = StrSave( appData.pgnEventHeader );
12837         gameInfo.site = StrSave(HostName());
12838         gameInfo.date = PGNDate();
12839         gameInfo.round = StrSave("-");
12840         gameInfo.white = StrSave(UserName());
12841         gameInfo.black = StrSave(first.tidy);
12842         gameInfo.timeControl = TimeControlTagValue();
12843         break;
12844
12845       case TwoMachinesPlay:
12846         gameInfo.event = StrSave( appData.pgnEventHeader );
12847         gameInfo.site = StrSave(HostName());
12848         gameInfo.date = PGNDate();
12849         if (matchGame > 0) {
12850             char buf[MSG_SIZ];
12851             sprintf(buf, "%d", matchGame);
12852             gameInfo.round = StrSave(buf);
12853         } else {
12854             gameInfo.round = StrSave("-");
12855         }
12856         if (first.twoMachinesColor[0] == 'w') {
12857             gameInfo.white = StrSave(first.tidy);
12858             gameInfo.black = StrSave(second.tidy);
12859         } else {
12860             gameInfo.white = StrSave(second.tidy);
12861             gameInfo.black = StrSave(first.tidy);
12862         }
12863         gameInfo.timeControl = TimeControlTagValue();
12864         break;
12865
12866       case EditGame:
12867         gameInfo.event = StrSave("Edited game");
12868         gameInfo.site = StrSave(HostName());
12869         gameInfo.date = PGNDate();
12870         gameInfo.round = StrSave("-");
12871         gameInfo.white = StrSave("-");
12872         gameInfo.black = StrSave("-");
12873         gameInfo.result = r;
12874         gameInfo.resultDetails = p;
12875         break;
12876
12877       case EditPosition:
12878         gameInfo.event = StrSave("Edited position");
12879         gameInfo.site = StrSave(HostName());
12880         gameInfo.date = PGNDate();
12881         gameInfo.round = StrSave("-");
12882         gameInfo.white = StrSave("-");
12883         gameInfo.black = StrSave("-");
12884         break;
12885
12886       case IcsPlayingWhite:
12887       case IcsPlayingBlack:
12888       case IcsObserving:
12889       case IcsExamining:
12890         break;
12891
12892       case PlayFromGameFile:
12893         gameInfo.event = StrSave("Game from non-PGN file");
12894         gameInfo.site = StrSave(HostName());
12895         gameInfo.date = PGNDate();
12896         gameInfo.round = StrSave("-");
12897         gameInfo.white = StrSave("?");
12898         gameInfo.black = StrSave("?");
12899         break;
12900
12901       default:
12902         break;
12903     }
12904 }
12905
12906 void
12907 ReplaceComment(index, text)
12908      int index;
12909      char *text;
12910 {
12911     int len;
12912
12913     while (*text == '\n') text++;
12914     len = strlen(text);
12915     while (len > 0 && text[len - 1] == '\n') len--;
12916
12917     if (commentList[index] != NULL)
12918       free(commentList[index]);
12919
12920     if (len == 0) {
12921         commentList[index] = NULL;
12922         return;
12923     }
12924   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12925       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12926       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12927     commentList[index] = (char *) malloc(len + 2);
12928     strncpy(commentList[index], text, len);
12929     commentList[index][len] = '\n';
12930     commentList[index][len + 1] = NULLCHAR;
12931   } else { 
12932     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12933     char *p;
12934     commentList[index] = (char *) malloc(len + 6);
12935     strcpy(commentList[index], "{\n");
12936     strncpy(commentList[index]+2, text, len);
12937     commentList[index][len+2] = NULLCHAR;
12938     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12939     strcat(commentList[index], "\n}\n");
12940   }
12941 }
12942
12943 void
12944 CrushCRs(text)
12945      char *text;
12946 {
12947   char *p = text;
12948   char *q = text;
12949   char ch;
12950
12951   do {
12952     ch = *p++;
12953     if (ch == '\r') continue;
12954     *q++ = ch;
12955   } while (ch != '\0');
12956 }
12957
12958 void
12959 AppendComment(index, text, addBraces)
12960      int index;
12961      char *text;
12962      Boolean addBraces; // [HGM] braces: tells if we should add {}
12963 {
12964     int oldlen, len;
12965     char *old;
12966
12967 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12968     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12969
12970     CrushCRs(text);
12971     while (*text == '\n') text++;
12972     len = strlen(text);
12973     while (len > 0 && text[len - 1] == '\n') len--;
12974
12975     if (len == 0) return;
12976
12977     if (commentList[index] != NULL) {
12978         old = commentList[index];
12979         oldlen = strlen(old);
12980         while(commentList[index][oldlen-1] ==  '\n')
12981           commentList[index][--oldlen] = NULLCHAR;
12982         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12983         strcpy(commentList[index], old);
12984         free(old);
12985         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12986         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12987           if(addBraces) addBraces = FALSE; else { text++; len--; }
12988           while (*text == '\n') { text++; len--; }
12989           commentList[index][--oldlen] = NULLCHAR;
12990       }
12991         if(addBraces) strcat(commentList[index], "\n{\n");
12992         else          strcat(commentList[index], "\n");
12993         strcat(commentList[index], text);
12994         if(addBraces) strcat(commentList[index], "\n}\n");
12995         else          strcat(commentList[index], "\n");
12996     } else {
12997         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12998         if(addBraces)
12999              strcpy(commentList[index], "{\n");
13000         else commentList[index][0] = NULLCHAR;
13001         strcat(commentList[index], text);
13002         strcat(commentList[index], "\n");
13003         if(addBraces) strcat(commentList[index], "}\n");
13004     }
13005 }
13006
13007 static char * FindStr( char * text, char * sub_text )
13008 {
13009     char * result = strstr( text, sub_text );
13010
13011     if( result != NULL ) {
13012         result += strlen( sub_text );
13013     }
13014
13015     return result;
13016 }
13017
13018 /* [AS] Try to extract PV info from PGN comment */
13019 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13020 char *GetInfoFromComment( int index, char * text )
13021 {
13022     char * sep = text;
13023
13024     if( text != NULL && index > 0 ) {
13025         int score = 0;
13026         int depth = 0;
13027         int time = -1, sec = 0, deci;
13028         char * s_eval = FindStr( text, "[%eval " );
13029         char * s_emt = FindStr( text, "[%emt " );
13030
13031         if( s_eval != NULL || s_emt != NULL ) {
13032             /* New style */
13033             char delim;
13034
13035             if( s_eval != NULL ) {
13036                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13037                     return text;
13038                 }
13039
13040                 if( delim != ']' ) {
13041                     return text;
13042                 }
13043             }
13044
13045             if( s_emt != NULL ) {
13046             }
13047                 return text;
13048         }
13049         else {
13050             /* We expect something like: [+|-]nnn.nn/dd */
13051             int score_lo = 0;
13052
13053             if(*text != '{') return text; // [HGM] braces: must be normal comment
13054
13055             sep = strchr( text, '/' );
13056             if( sep == NULL || sep < (text+4) ) {
13057                 return text;
13058             }
13059
13060             time = -1; sec = -1; deci = -1;
13061             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13062                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13063                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13064                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13065                 return text;
13066             }
13067
13068             if( score_lo < 0 || score_lo >= 100 ) {
13069                 return text;
13070             }
13071
13072             if(sec >= 0) time = 600*time + 10*sec; else
13073             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13074
13075             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13076
13077             /* [HGM] PV time: now locate end of PV info */
13078             while( *++sep >= '0' && *sep <= '9'); // strip depth
13079             if(time >= 0)
13080             while( *++sep >= '0' && *sep <= '9'); // strip time
13081             if(sec >= 0)
13082             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13083             if(deci >= 0)
13084             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13085             while(*sep == ' ') sep++;
13086         }
13087
13088         if( depth <= 0 ) {
13089             return text;
13090         }
13091
13092         if( time < 0 ) {
13093             time = -1;
13094         }
13095
13096         pvInfoList[index-1].depth = depth;
13097         pvInfoList[index-1].score = score;
13098         pvInfoList[index-1].time  = 10*time; // centi-sec
13099         if(*sep == '}') *sep = 0; else *--sep = '{';
13100     }
13101     return sep;
13102 }
13103
13104 void
13105 SendToProgram(message, cps)
13106      char *message;
13107      ChessProgramState *cps;
13108 {
13109     int count, outCount, error;
13110     char buf[MSG_SIZ];
13111
13112     if (cps->pr == NULL) return;
13113     Attention(cps);
13114     
13115     if (appData.debugMode) {
13116         TimeMark now;
13117         GetTimeMark(&now);
13118         fprintf(debugFP, "%ld >%-6s: %s", 
13119                 SubtractTimeMarks(&now, &programStartTime),
13120                 cps->which, message);
13121     }
13122     
13123     count = strlen(message);
13124     outCount = OutputToProcess(cps->pr, message, count, &error);
13125     if (outCount < count && !exiting 
13126                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13127         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13128         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13129             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13130                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13131                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13132             } else {
13133                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13134             }
13135             gameInfo.resultDetails = StrSave(buf);
13136         }
13137         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13138     }
13139 }
13140
13141 void
13142 ReceiveFromProgram(isr, closure, message, count, error)
13143      InputSourceRef isr;
13144      VOIDSTAR closure;
13145      char *message;
13146      int count;
13147      int error;
13148 {
13149     char *end_str;
13150     char buf[MSG_SIZ];
13151     ChessProgramState *cps = (ChessProgramState *)closure;
13152
13153     if (isr != cps->isr) return; /* Killed intentionally */
13154     if (count <= 0) {
13155         if (count == 0) {
13156             sprintf(buf,
13157                     _("Error: %s chess program (%s) exited unexpectedly"),
13158                     cps->which, cps->program);
13159         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13160                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13161                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13162                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13163                 } else {
13164                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13165                 }
13166                 gameInfo.resultDetails = StrSave(buf);
13167             }
13168             RemoveInputSource(cps->isr);
13169             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13170         } else {
13171             sprintf(buf,
13172                     _("Error reading from %s chess program (%s)"),
13173                     cps->which, cps->program);
13174             RemoveInputSource(cps->isr);
13175
13176             /* [AS] Program is misbehaving badly... kill it */
13177             if( count == -2 ) {
13178                 DestroyChildProcess( cps->pr, 9 );
13179                 cps->pr = NoProc;
13180             }
13181
13182             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13183         }
13184         return;
13185     }
13186     
13187     if ((end_str = strchr(message, '\r')) != NULL)
13188       *end_str = NULLCHAR;
13189     if ((end_str = strchr(message, '\n')) != NULL)
13190       *end_str = NULLCHAR;
13191     
13192     if (appData.debugMode) {
13193         TimeMark now; int print = 1;
13194         char *quote = ""; char c; int i;
13195
13196         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13197                 char start = message[0];
13198                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13199                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13200                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13201                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13202                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13203                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13204                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13205                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13206                         { quote = "# "; print = (appData.engineComments == 2); }
13207                 message[0] = start; // restore original message
13208         }
13209         if(print) {
13210                 GetTimeMark(&now);
13211                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13212                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13213                         quote,
13214                         message);
13215         }
13216     }
13217
13218     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13219     if (appData.icsEngineAnalyze) {
13220         if (strstr(message, "whisper") != NULL ||
13221              strstr(message, "kibitz") != NULL || 
13222             strstr(message, "tellics") != NULL) return;
13223     }
13224
13225     HandleMachineMove(message, cps);
13226 }
13227
13228
13229 void
13230 SendTimeControl(cps, mps, tc, inc, sd, st)
13231      ChessProgramState *cps;
13232      int mps, inc, sd, st;
13233      long tc;
13234 {
13235     char buf[MSG_SIZ];
13236     int seconds;
13237
13238     if( timeControl_2 > 0 ) {
13239         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13240             tc = timeControl_2;
13241         }
13242     }
13243     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13244     inc /= cps->timeOdds;
13245     st  /= cps->timeOdds;
13246
13247     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13248
13249     if (st > 0) {
13250       /* Set exact time per move, normally using st command */
13251       if (cps->stKludge) {
13252         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13253         seconds = st % 60;
13254         if (seconds == 0) {
13255           sprintf(buf, "level 1 %d\n", st/60);
13256         } else {
13257           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13258         }
13259       } else {
13260         sprintf(buf, "st %d\n", st);
13261       }
13262     } else {
13263       /* Set conventional or incremental time control, using level command */
13264       if (seconds == 0) {
13265         /* Note old gnuchess bug -- minutes:seconds used to not work.
13266            Fixed in later versions, but still avoid :seconds
13267            when seconds is 0. */
13268         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13269       } else {
13270         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13271                 seconds, inc/1000);
13272       }
13273     }
13274     SendToProgram(buf, cps);
13275
13276     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13277     /* Orthogonally, limit search to given depth */
13278     if (sd > 0) {
13279       if (cps->sdKludge) {
13280         sprintf(buf, "depth\n%d\n", sd);
13281       } else {
13282         sprintf(buf, "sd %d\n", sd);
13283       }
13284       SendToProgram(buf, cps);
13285     }
13286
13287     if(cps->nps > 0) { /* [HGM] nps */
13288         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13289         else {
13290                 sprintf(buf, "nps %d\n", cps->nps);
13291               SendToProgram(buf, cps);
13292         }
13293     }
13294 }
13295
13296 ChessProgramState *WhitePlayer()
13297 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13298 {
13299     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13300        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13301         return &second;
13302     return &first;
13303 }
13304
13305 void
13306 SendTimeRemaining(cps, machineWhite)
13307      ChessProgramState *cps;
13308      int /*boolean*/ machineWhite;
13309 {
13310     char message[MSG_SIZ];
13311     long time, otime;
13312
13313     /* Note: this routine must be called when the clocks are stopped
13314        or when they have *just* been set or switched; otherwise
13315        it will be off by the time since the current tick started.
13316     */
13317     if (machineWhite) {
13318         time = whiteTimeRemaining / 10;
13319         otime = blackTimeRemaining / 10;
13320     } else {
13321         time = blackTimeRemaining / 10;
13322         otime = whiteTimeRemaining / 10;
13323     }
13324     /* [HGM] translate opponent's time by time-odds factor */
13325     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13326     if (appData.debugMode) {
13327         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13328     }
13329
13330     if (time <= 0) time = 1;
13331     if (otime <= 0) otime = 1;
13332     
13333     sprintf(message, "time %ld\n", time);
13334     SendToProgram(message, cps);
13335
13336     sprintf(message, "otim %ld\n", otime);
13337     SendToProgram(message, cps);
13338 }
13339
13340 int
13341 BoolFeature(p, name, loc, cps)
13342      char **p;
13343      char *name;
13344      int *loc;
13345      ChessProgramState *cps;
13346 {
13347   char buf[MSG_SIZ];
13348   int len = strlen(name);
13349   int val;
13350   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13351     (*p) += len + 1;
13352     sscanf(*p, "%d", &val);
13353     *loc = (val != 0);
13354     while (**p && **p != ' ') (*p)++;
13355     sprintf(buf, "accepted %s\n", name);
13356     SendToProgram(buf, cps);
13357     return TRUE;
13358   }
13359   return FALSE;
13360 }
13361
13362 int
13363 IntFeature(p, name, loc, cps)
13364      char **p;
13365      char *name;
13366      int *loc;
13367      ChessProgramState *cps;
13368 {
13369   char buf[MSG_SIZ];
13370   int len = strlen(name);
13371   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13372     (*p) += len + 1;
13373     sscanf(*p, "%d", loc);
13374     while (**p && **p != ' ') (*p)++;
13375     sprintf(buf, "accepted %s\n", name);
13376     SendToProgram(buf, cps);
13377     return TRUE;
13378   }
13379   return FALSE;
13380 }
13381
13382 int
13383 StringFeature(p, name, loc, cps)
13384      char **p;
13385      char *name;
13386      char loc[];
13387      ChessProgramState *cps;
13388 {
13389   char buf[MSG_SIZ];
13390   int len = strlen(name);
13391   if (strncmp((*p), name, len) == 0
13392       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13393     (*p) += len + 2;
13394     sscanf(*p, "%[^\"]", loc);
13395     while (**p && **p != '\"') (*p)++;
13396     if (**p == '\"') (*p)++;
13397     sprintf(buf, "accepted %s\n", name);
13398     SendToProgram(buf, cps);
13399     return TRUE;
13400   }
13401   return FALSE;
13402 }
13403
13404 int 
13405 ParseOption(Option *opt, ChessProgramState *cps)
13406 // [HGM] options: process the string that defines an engine option, and determine
13407 // name, type, default value, and allowed value range
13408 {
13409         char *p, *q, buf[MSG_SIZ];
13410         int n, min = (-1)<<31, max = 1<<31, def;
13411
13412         if(p = strstr(opt->name, " -spin ")) {
13413             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13414             if(max < min) max = min; // enforce consistency
13415             if(def < min) def = min;
13416             if(def > max) def = max;
13417             opt->value = def;
13418             opt->min = min;
13419             opt->max = max;
13420             opt->type = Spin;
13421         } else if((p = strstr(opt->name, " -slider "))) {
13422             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13423             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13424             if(max < min) max = min; // enforce consistency
13425             if(def < min) def = min;
13426             if(def > max) def = max;
13427             opt->value = def;
13428             opt->min = min;
13429             opt->max = max;
13430             opt->type = Spin; // Slider;
13431         } else if((p = strstr(opt->name, " -string "))) {
13432             opt->textValue = p+9;
13433             opt->type = TextBox;
13434         } else if((p = strstr(opt->name, " -file "))) {
13435             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13436             opt->textValue = p+7;
13437             opt->type = TextBox; // FileName;
13438         } else if((p = strstr(opt->name, " -path "))) {
13439             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13440             opt->textValue = p+7;
13441             opt->type = TextBox; // PathName;
13442         } else if(p = strstr(opt->name, " -check ")) {
13443             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13444             opt->value = (def != 0);
13445             opt->type = CheckBox;
13446         } else if(p = strstr(opt->name, " -combo ")) {
13447             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13448             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13449             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13450             opt->value = n = 0;
13451             while(q = StrStr(q, " /// ")) {
13452                 n++; *q = 0;    // count choices, and null-terminate each of them
13453                 q += 5;
13454                 if(*q == '*') { // remember default, which is marked with * prefix
13455                     q++;
13456                     opt->value = n;
13457                 }
13458                 cps->comboList[cps->comboCnt++] = q;
13459             }
13460             cps->comboList[cps->comboCnt++] = NULL;
13461             opt->max = n + 1;
13462             opt->type = ComboBox;
13463         } else if(p = strstr(opt->name, " -button")) {
13464             opt->type = Button;
13465         } else if(p = strstr(opt->name, " -save")) {
13466             opt->type = SaveButton;
13467         } else return FALSE;
13468         *p = 0; // terminate option name
13469         // now look if the command-line options define a setting for this engine option.
13470         if(cps->optionSettings && cps->optionSettings[0])
13471             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13472         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13473                 sprintf(buf, "option %s", p);
13474                 if(p = strstr(buf, ",")) *p = 0;
13475                 strcat(buf, "\n");
13476                 SendToProgram(buf, cps);
13477         }
13478         return TRUE;
13479 }
13480
13481 void
13482 FeatureDone(cps, val)
13483      ChessProgramState* cps;
13484      int val;
13485 {
13486   DelayedEventCallback cb = GetDelayedEvent();
13487   if ((cb == InitBackEnd3 && cps == &first) ||
13488       (cb == TwoMachinesEventIfReady && cps == &second)) {
13489     CancelDelayedEvent();
13490     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13491   }
13492   cps->initDone = val;
13493 }
13494
13495 /* Parse feature command from engine */
13496 void
13497 ParseFeatures(args, cps)
13498      char* args;
13499      ChessProgramState *cps;  
13500 {
13501   char *p = args;
13502   char *q;
13503   int val;
13504   char buf[MSG_SIZ];
13505
13506   for (;;) {
13507     while (*p == ' ') p++;
13508     if (*p == NULLCHAR) return;
13509
13510     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13511     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13512     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13513     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13514     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13515     if (BoolFeature(&p, "reuse", &val, cps)) {
13516       /* Engine can disable reuse, but can't enable it if user said no */
13517       if (!val) cps->reuse = FALSE;
13518       continue;
13519     }
13520     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13521     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13522       if (gameMode == TwoMachinesPlay) {
13523         DisplayTwoMachinesTitle();
13524       } else {
13525         DisplayTitle("");
13526       }
13527       continue;
13528     }
13529     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13530     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13531     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13532     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13533     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13534     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13535     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13536     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13537     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13538     if (IntFeature(&p, "done", &val, cps)) {
13539       FeatureDone(cps, val);
13540       continue;
13541     }
13542     /* Added by Tord: */
13543     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13544     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13545     /* End of additions by Tord */
13546
13547     /* [HGM] added features: */
13548     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13549     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13550     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13551     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13552     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13553     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13554     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13555         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13556             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13557             SendToProgram(buf, cps);
13558             continue;
13559         }
13560         if(cps->nrOptions >= MAX_OPTIONS) {
13561             cps->nrOptions--;
13562             sprintf(buf, "%s engine has too many options\n", cps->which);
13563             DisplayError(buf, 0);
13564         }
13565         continue;
13566     }
13567     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13568     /* End of additions by HGM */
13569
13570     /* unknown feature: complain and skip */
13571     q = p;
13572     while (*q && *q != '=') q++;
13573     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13574     SendToProgram(buf, cps);
13575     p = q;
13576     if (*p == '=') {
13577       p++;
13578       if (*p == '\"') {
13579         p++;
13580         while (*p && *p != '\"') p++;
13581         if (*p == '\"') p++;
13582       } else {
13583         while (*p && *p != ' ') p++;
13584       }
13585     }
13586   }
13587
13588 }
13589
13590 void
13591 PeriodicUpdatesEvent(newState)
13592      int newState;
13593 {
13594     if (newState == appData.periodicUpdates)
13595       return;
13596
13597     appData.periodicUpdates=newState;
13598
13599     /* Display type changes, so update it now */
13600 //    DisplayAnalysis();
13601
13602     /* Get the ball rolling again... */
13603     if (newState) {
13604         AnalysisPeriodicEvent(1);
13605         StartAnalysisClock();
13606     }
13607 }
13608
13609 void
13610 PonderNextMoveEvent(newState)
13611      int newState;
13612 {
13613     if (newState == appData.ponderNextMove) return;
13614     if (gameMode == EditPosition) EditPositionDone(TRUE);
13615     if (newState) {
13616         SendToProgram("hard\n", &first);
13617         if (gameMode == TwoMachinesPlay) {
13618             SendToProgram("hard\n", &second);
13619         }
13620     } else {
13621         SendToProgram("easy\n", &first);
13622         thinkOutput[0] = NULLCHAR;
13623         if (gameMode == TwoMachinesPlay) {
13624             SendToProgram("easy\n", &second);
13625         }
13626     }
13627     appData.ponderNextMove = newState;
13628 }
13629
13630 void
13631 NewSettingEvent(option, command, value)
13632      char *command;
13633      int option, value;
13634 {
13635     char buf[MSG_SIZ];
13636
13637     if (gameMode == EditPosition) EditPositionDone(TRUE);
13638     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13639     SendToProgram(buf, &first);
13640     if (gameMode == TwoMachinesPlay) {
13641         SendToProgram(buf, &second);
13642     }
13643 }
13644
13645 void
13646 ShowThinkingEvent()
13647 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13648 {
13649     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13650     int newState = appData.showThinking
13651         // [HGM] thinking: other features now need thinking output as well
13652         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13653     
13654     if (oldState == newState) return;
13655     oldState = newState;
13656     if (gameMode == EditPosition) EditPositionDone(TRUE);
13657     if (oldState) {
13658         SendToProgram("post\n", &first);
13659         if (gameMode == TwoMachinesPlay) {
13660             SendToProgram("post\n", &second);
13661         }
13662     } else {
13663         SendToProgram("nopost\n", &first);
13664         thinkOutput[0] = NULLCHAR;
13665         if (gameMode == TwoMachinesPlay) {
13666             SendToProgram("nopost\n", &second);
13667         }
13668     }
13669 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13670 }
13671
13672 void
13673 AskQuestionEvent(title, question, replyPrefix, which)
13674      char *title; char *question; char *replyPrefix; char *which;
13675 {
13676   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13677   if (pr == NoProc) return;
13678   AskQuestion(title, question, replyPrefix, pr);
13679 }
13680
13681 void
13682 DisplayMove(moveNumber)
13683      int moveNumber;
13684 {
13685     char message[MSG_SIZ];
13686     char res[MSG_SIZ];
13687     char cpThinkOutput[MSG_SIZ];
13688
13689     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13690     
13691     if (moveNumber == forwardMostMove - 1 || 
13692         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13693
13694         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13695
13696         if (strchr(cpThinkOutput, '\n')) {
13697             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13698         }
13699     } else {
13700         *cpThinkOutput = NULLCHAR;
13701     }
13702
13703     /* [AS] Hide thinking from human user */
13704     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13705         *cpThinkOutput = NULLCHAR;
13706         if( thinkOutput[0] != NULLCHAR ) {
13707             int i;
13708
13709             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13710                 cpThinkOutput[i] = '.';
13711             }
13712             cpThinkOutput[i] = NULLCHAR;
13713             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13714         }
13715     }
13716
13717     if (moveNumber == forwardMostMove - 1 &&
13718         gameInfo.resultDetails != NULL) {
13719         if (gameInfo.resultDetails[0] == NULLCHAR) {
13720             sprintf(res, " %s", PGNResult(gameInfo.result));
13721         } else {
13722             sprintf(res, " {%s} %s",
13723                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13724         }
13725     } else {
13726         res[0] = NULLCHAR;
13727     }
13728
13729     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13730         DisplayMessage(res, cpThinkOutput);
13731     } else {
13732         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13733                 WhiteOnMove(moveNumber) ? " " : ".. ",
13734                 parseList[moveNumber], res);
13735         DisplayMessage(message, cpThinkOutput);
13736     }
13737 }
13738
13739 void
13740 DisplayComment(moveNumber, text)
13741      int moveNumber;
13742      char *text;
13743 {
13744     char title[MSG_SIZ];
13745     char buf[8000]; // comment can be long!
13746     int score, depth;
13747     
13748     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13749       strcpy(title, "Comment");
13750     } else {
13751       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13752               WhiteOnMove(moveNumber) ? " " : ".. ",
13753               parseList[moveNumber]);
13754     }
13755     // [HGM] PV info: display PV info together with (or as) comment
13756     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13757       if(text == NULL) text = "";                                           
13758       score = pvInfoList[moveNumber].score;
13759       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13760               depth, (pvInfoList[moveNumber].time+50)/100, text);
13761       text = buf;
13762     }
13763     if (text != NULL && (appData.autoDisplayComment || commentUp))
13764         CommentPopUp(title, text);
13765 }
13766
13767 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13768  * might be busy thinking or pondering.  It can be omitted if your
13769  * gnuchess is configured to stop thinking immediately on any user
13770  * input.  However, that gnuchess feature depends on the FIONREAD
13771  * ioctl, which does not work properly on some flavors of Unix.
13772  */
13773 void
13774 Attention(cps)
13775      ChessProgramState *cps;
13776 {
13777 #if ATTENTION
13778     if (!cps->useSigint) return;
13779     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13780     switch (gameMode) {
13781       case MachinePlaysWhite:
13782       case MachinePlaysBlack:
13783       case TwoMachinesPlay:
13784       case IcsPlayingWhite:
13785       case IcsPlayingBlack:
13786       case AnalyzeMode:
13787       case AnalyzeFile:
13788         /* Skip if we know it isn't thinking */
13789         if (!cps->maybeThinking) return;
13790         if (appData.debugMode)
13791           fprintf(debugFP, "Interrupting %s\n", cps->which);
13792         InterruptChildProcess(cps->pr);
13793         cps->maybeThinking = FALSE;
13794         break;
13795       default:
13796         break;
13797     }
13798 #endif /*ATTENTION*/
13799 }
13800
13801 int
13802 CheckFlags()
13803 {
13804     if (whiteTimeRemaining <= 0) {
13805         if (!whiteFlag) {
13806             whiteFlag = TRUE;
13807             if (appData.icsActive) {
13808                 if (appData.autoCallFlag &&
13809                     gameMode == IcsPlayingBlack && !blackFlag) {
13810                   SendToICS(ics_prefix);
13811                   SendToICS("flag\n");
13812                 }
13813             } else {
13814                 if (blackFlag) {
13815                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13816                 } else {
13817                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13818                     if (appData.autoCallFlag) {
13819                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13820                         return TRUE;
13821                     }
13822                 }
13823             }
13824         }
13825     }
13826     if (blackTimeRemaining <= 0) {
13827         if (!blackFlag) {
13828             blackFlag = TRUE;
13829             if (appData.icsActive) {
13830                 if (appData.autoCallFlag &&
13831                     gameMode == IcsPlayingWhite && !whiteFlag) {
13832                   SendToICS(ics_prefix);
13833                   SendToICS("flag\n");
13834                 }
13835             } else {
13836                 if (whiteFlag) {
13837                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13838                 } else {
13839                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13840                     if (appData.autoCallFlag) {
13841                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13842                         return TRUE;
13843                     }
13844                 }
13845             }
13846         }
13847     }
13848     return FALSE;
13849 }
13850
13851 void
13852 CheckTimeControl()
13853 {
13854     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13855         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13856
13857     /*
13858      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13859      */
13860     if ( !WhiteOnMove(forwardMostMove) )
13861         /* White made time control */
13862         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13863         /* [HGM] time odds: correct new time quota for time odds! */
13864                                             / WhitePlayer()->timeOdds;
13865       else
13866         /* Black made time control */
13867         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13868                                             / WhitePlayer()->other->timeOdds;
13869 }
13870
13871 void
13872 DisplayBothClocks()
13873 {
13874     int wom = gameMode == EditPosition ?
13875       !blackPlaysFirst : WhiteOnMove(currentMove);
13876     DisplayWhiteClock(whiteTimeRemaining, wom);
13877     DisplayBlackClock(blackTimeRemaining, !wom);
13878 }
13879
13880
13881 /* Timekeeping seems to be a portability nightmare.  I think everyone
13882    has ftime(), but I'm really not sure, so I'm including some ifdefs
13883    to use other calls if you don't.  Clocks will be less accurate if
13884    you have neither ftime nor gettimeofday.
13885 */
13886
13887 /* VS 2008 requires the #include outside of the function */
13888 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13889 #include <sys/timeb.h>
13890 #endif
13891
13892 /* Get the current time as a TimeMark */
13893 void
13894 GetTimeMark(tm)
13895      TimeMark *tm;
13896 {
13897 #if HAVE_GETTIMEOFDAY
13898
13899     struct timeval timeVal;
13900     struct timezone timeZone;
13901
13902     gettimeofday(&timeVal, &timeZone);
13903     tm->sec = (long) timeVal.tv_sec; 
13904     tm->ms = (int) (timeVal.tv_usec / 1000L);
13905
13906 #else /*!HAVE_GETTIMEOFDAY*/
13907 #if HAVE_FTIME
13908
13909 // include <sys/timeb.h> / moved to just above start of function
13910     struct timeb timeB;
13911
13912     ftime(&timeB);
13913     tm->sec = (long) timeB.time;
13914     tm->ms = (int) timeB.millitm;
13915
13916 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13917     tm->sec = (long) time(NULL);
13918     tm->ms = 0;
13919 #endif
13920 #endif
13921 }
13922
13923 /* Return the difference in milliseconds between two
13924    time marks.  We assume the difference will fit in a long!
13925 */
13926 long
13927 SubtractTimeMarks(tm2, tm1)
13928      TimeMark *tm2, *tm1;
13929 {
13930     return 1000L*(tm2->sec - tm1->sec) +
13931            (long) (tm2->ms - tm1->ms);
13932 }
13933
13934
13935 /*
13936  * Code to manage the game clocks.
13937  *
13938  * In tournament play, black starts the clock and then white makes a move.
13939  * We give the human user a slight advantage if he is playing white---the
13940  * clocks don't run until he makes his first move, so it takes zero time.
13941  * Also, we don't account for network lag, so we could get out of sync
13942  * with GNU Chess's clock -- but then, referees are always right.  
13943  */
13944
13945 static TimeMark tickStartTM;
13946 static long intendedTickLength;
13947
13948 long
13949 NextTickLength(timeRemaining)
13950      long timeRemaining;
13951 {
13952     long nominalTickLength, nextTickLength;
13953
13954     if (timeRemaining > 0L && timeRemaining <= 10000L)
13955       nominalTickLength = 100L;
13956     else
13957       nominalTickLength = 1000L;
13958     nextTickLength = timeRemaining % nominalTickLength;
13959     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13960
13961     return nextTickLength;
13962 }
13963
13964 /* Adjust clock one minute up or down */
13965 void
13966 AdjustClock(Boolean which, int dir)
13967 {
13968     if(which) blackTimeRemaining += 60000*dir;
13969     else      whiteTimeRemaining += 60000*dir;
13970     DisplayBothClocks();
13971 }
13972
13973 /* Stop clocks and reset to a fresh time control */
13974 void
13975 ResetClocks() 
13976 {
13977     (void) StopClockTimer();
13978     if (appData.icsActive) {
13979         whiteTimeRemaining = blackTimeRemaining = 0;
13980     } else if (searchTime) {
13981         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13982         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13983     } else { /* [HGM] correct new time quote for time odds */
13984         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13985         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13986     }
13987     if (whiteFlag || blackFlag) {
13988         DisplayTitle("");
13989         whiteFlag = blackFlag = FALSE;
13990     }
13991     DisplayBothClocks();
13992 }
13993
13994 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13995
13996 /* Decrement running clock by amount of time that has passed */
13997 void
13998 DecrementClocks()
13999 {
14000     long timeRemaining;
14001     long lastTickLength, fudge;
14002     TimeMark now;
14003
14004     if (!appData.clockMode) return;
14005     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14006         
14007     GetTimeMark(&now);
14008
14009     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14010
14011     /* Fudge if we woke up a little too soon */
14012     fudge = intendedTickLength - lastTickLength;
14013     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14014
14015     if (WhiteOnMove(forwardMostMove)) {
14016         if(whiteNPS >= 0) lastTickLength = 0;
14017         timeRemaining = whiteTimeRemaining -= lastTickLength;
14018         DisplayWhiteClock(whiteTimeRemaining - fudge,
14019                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14020     } else {
14021         if(blackNPS >= 0) lastTickLength = 0;
14022         timeRemaining = blackTimeRemaining -= lastTickLength;
14023         DisplayBlackClock(blackTimeRemaining - fudge,
14024                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14025     }
14026
14027     if (CheckFlags()) return;
14028         
14029     tickStartTM = now;
14030     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14031     StartClockTimer(intendedTickLength);
14032
14033     /* if the time remaining has fallen below the alarm threshold, sound the
14034      * alarm. if the alarm has sounded and (due to a takeback or time control
14035      * with increment) the time remaining has increased to a level above the
14036      * threshold, reset the alarm so it can sound again. 
14037      */
14038     
14039     if (appData.icsActive && appData.icsAlarm) {
14040
14041         /* make sure we are dealing with the user's clock */
14042         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14043                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14044            )) return;
14045
14046         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14047             alarmSounded = FALSE;
14048         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14049             PlayAlarmSound();
14050             alarmSounded = TRUE;
14051         }
14052     }
14053 }
14054
14055
14056 /* A player has just moved, so stop the previously running
14057    clock and (if in clock mode) start the other one.
14058    We redisplay both clocks in case we're in ICS mode, because
14059    ICS gives us an update to both clocks after every move.
14060    Note that this routine is called *after* forwardMostMove
14061    is updated, so the last fractional tick must be subtracted
14062    from the color that is *not* on move now.
14063 */
14064 void
14065 SwitchClocks(int newMoveNr)
14066 {
14067     long lastTickLength;
14068     TimeMark now;
14069     int flagged = FALSE;
14070
14071     GetTimeMark(&now);
14072
14073     if (StopClockTimer() && appData.clockMode) {
14074         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14075         if (!WhiteOnMove(forwardMostMove)) {
14076             if(blackNPS >= 0) lastTickLength = 0;
14077             blackTimeRemaining -= lastTickLength;
14078            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14079 //         if(pvInfoList[forwardMostMove-1].time == -1)
14080                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14081                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14082         } else {
14083            if(whiteNPS >= 0) lastTickLength = 0;
14084            whiteTimeRemaining -= lastTickLength;
14085            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14086 //         if(pvInfoList[forwardMostMove-1].time == -1)
14087                  pvInfoList[forwardMostMove-1].time = 
14088                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14089         }
14090         flagged = CheckFlags();
14091     }
14092     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14093     CheckTimeControl();
14094
14095     if (flagged || !appData.clockMode) return;
14096
14097     switch (gameMode) {
14098       case MachinePlaysBlack:
14099       case MachinePlaysWhite:
14100       case BeginningOfGame:
14101         if (pausing) return;
14102         break;
14103
14104       case EditGame:
14105       case PlayFromGameFile:
14106       case IcsExamining:
14107         return;
14108
14109       default:
14110         break;
14111     }
14112
14113     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14114         if(WhiteOnMove(forwardMostMove))
14115              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14116         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14117     }
14118
14119     tickStartTM = now;
14120     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14121       whiteTimeRemaining : blackTimeRemaining);
14122     StartClockTimer(intendedTickLength);
14123 }
14124         
14125
14126 /* Stop both clocks */
14127 void
14128 StopClocks()
14129 {       
14130     long lastTickLength;
14131     TimeMark now;
14132
14133     if (!StopClockTimer()) return;
14134     if (!appData.clockMode) return;
14135
14136     GetTimeMark(&now);
14137
14138     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14139     if (WhiteOnMove(forwardMostMove)) {
14140         if(whiteNPS >= 0) lastTickLength = 0;
14141         whiteTimeRemaining -= lastTickLength;
14142         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14143     } else {
14144         if(blackNPS >= 0) lastTickLength = 0;
14145         blackTimeRemaining -= lastTickLength;
14146         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14147     }
14148     CheckFlags();
14149 }
14150         
14151 /* Start clock of player on move.  Time may have been reset, so
14152    if clock is already running, stop and restart it. */
14153 void
14154 StartClocks()
14155 {
14156     (void) StopClockTimer(); /* in case it was running already */
14157     DisplayBothClocks();
14158     if (CheckFlags()) return;
14159
14160     if (!appData.clockMode) return;
14161     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14162
14163     GetTimeMark(&tickStartTM);
14164     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14165       whiteTimeRemaining : blackTimeRemaining);
14166
14167    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14168     whiteNPS = blackNPS = -1; 
14169     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14170        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14171         whiteNPS = first.nps;
14172     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14173        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14174         blackNPS = first.nps;
14175     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14176         whiteNPS = second.nps;
14177     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14178         blackNPS = second.nps;
14179     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14180
14181     StartClockTimer(intendedTickLength);
14182 }
14183
14184 char *
14185 TimeString(ms)
14186      long ms;
14187 {
14188     long second, minute, hour, day;
14189     char *sign = "";
14190     static char buf[32];
14191     
14192     if (ms > 0 && ms <= 9900) {
14193       /* convert milliseconds to tenths, rounding up */
14194       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14195
14196       sprintf(buf, " %03.1f ", tenths/10.0);
14197       return buf;
14198     }
14199
14200     /* convert milliseconds to seconds, rounding up */
14201     /* use floating point to avoid strangeness of integer division
14202        with negative dividends on many machines */
14203     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14204
14205     if (second < 0) {
14206         sign = "-";
14207         second = -second;
14208     }
14209     
14210     day = second / (60 * 60 * 24);
14211     second = second % (60 * 60 * 24);
14212     hour = second / (60 * 60);
14213     second = second % (60 * 60);
14214     minute = second / 60;
14215     second = second % 60;
14216     
14217     if (day > 0)
14218       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14219               sign, day, hour, minute, second);
14220     else if (hour > 0)
14221       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14222     else
14223       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14224     
14225     return buf;
14226 }
14227
14228
14229 /*
14230  * This is necessary because some C libraries aren't ANSI C compliant yet.
14231  */
14232 char *
14233 StrStr(string, match)
14234      char *string, *match;
14235 {
14236     int i, length;
14237     
14238     length = strlen(match);
14239     
14240     for (i = strlen(string) - length; i >= 0; i--, string++)
14241       if (!strncmp(match, string, length))
14242         return string;
14243     
14244     return NULL;
14245 }
14246
14247 char *
14248 StrCaseStr(string, match)
14249      char *string, *match;
14250 {
14251     int i, j, length;
14252     
14253     length = strlen(match);
14254     
14255     for (i = strlen(string) - length; i >= 0; i--, string++) {
14256         for (j = 0; j < length; j++) {
14257             if (ToLower(match[j]) != ToLower(string[j]))
14258               break;
14259         }
14260         if (j == length) return string;
14261     }
14262
14263     return NULL;
14264 }
14265
14266 #ifndef _amigados
14267 int
14268 StrCaseCmp(s1, s2)
14269      char *s1, *s2;
14270 {
14271     char c1, c2;
14272     
14273     for (;;) {
14274         c1 = ToLower(*s1++);
14275         c2 = ToLower(*s2++);
14276         if (c1 > c2) return 1;
14277         if (c1 < c2) return -1;
14278         if (c1 == NULLCHAR) return 0;
14279     }
14280 }
14281
14282
14283 int
14284 ToLower(c)
14285      int c;
14286 {
14287     return isupper(c) ? tolower(c) : c;
14288 }
14289
14290
14291 int
14292 ToUpper(c)
14293      int c;
14294 {
14295     return islower(c) ? toupper(c) : c;
14296 }
14297 #endif /* !_amigados    */
14298
14299 char *
14300 StrSave(s)
14301      char *s;
14302 {
14303     char *ret;
14304
14305     if ((ret = (char *) malloc(strlen(s) + 1))) {
14306         strcpy(ret, s);
14307     }
14308     return ret;
14309 }
14310
14311 char *
14312 StrSavePtr(s, savePtr)
14313      char *s, **savePtr;
14314 {
14315     if (*savePtr) {
14316         free(*savePtr);
14317     }
14318     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14319         strcpy(*savePtr, s);
14320     }
14321     return(*savePtr);
14322 }
14323
14324 char *
14325 PGNDate()
14326 {
14327     time_t clock;
14328     struct tm *tm;
14329     char buf[MSG_SIZ];
14330
14331     clock = time((time_t *)NULL);
14332     tm = localtime(&clock);
14333     sprintf(buf, "%04d.%02d.%02d",
14334             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14335     return StrSave(buf);
14336 }
14337
14338
14339 char *
14340 PositionToFEN(move, overrideCastling)
14341      int move;
14342      char *overrideCastling;
14343 {
14344     int i, j, fromX, fromY, toX, toY;
14345     int whiteToPlay;
14346     char buf[128];
14347     char *p, *q;
14348     int emptycount;
14349     ChessSquare piece;
14350
14351     whiteToPlay = (gameMode == EditPosition) ?
14352       !blackPlaysFirst : (move % 2 == 0);
14353     p = buf;
14354
14355     /* Piece placement data */
14356     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14357         emptycount = 0;
14358         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14359             if (boards[move][i][j] == EmptySquare) {
14360                 emptycount++;
14361             } else { ChessSquare piece = boards[move][i][j];
14362                 if (emptycount > 0) {
14363                     if(emptycount<10) /* [HGM] can be >= 10 */
14364                         *p++ = '0' + emptycount;
14365                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14366                     emptycount = 0;
14367                 }
14368                 if(PieceToChar(piece) == '+') {
14369                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14370                     *p++ = '+';
14371                     piece = (ChessSquare)(DEMOTED piece);
14372                 } 
14373                 *p++ = PieceToChar(piece);
14374                 if(p[-1] == '~') {
14375                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14376                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14377                     *p++ = '~';
14378                 }
14379             }
14380         }
14381         if (emptycount > 0) {
14382             if(emptycount<10) /* [HGM] can be >= 10 */
14383                 *p++ = '0' + emptycount;
14384             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14385             emptycount = 0;
14386         }
14387         *p++ = '/';
14388     }
14389     *(p - 1) = ' ';
14390
14391     /* [HGM] print Crazyhouse or Shogi holdings */
14392     if( gameInfo.holdingsWidth ) {
14393         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14394         q = p;
14395         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14396             piece = boards[move][i][BOARD_WIDTH-1];
14397             if( piece != EmptySquare )
14398               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14399                   *p++ = PieceToChar(piece);
14400         }
14401         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14402             piece = boards[move][BOARD_HEIGHT-i-1][0];
14403             if( piece != EmptySquare )
14404               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14405                   *p++ = PieceToChar(piece);
14406         }
14407
14408         if( q == p ) *p++ = '-';
14409         *p++ = ']';
14410         *p++ = ' ';
14411     }
14412
14413     /* Active color */
14414     *p++ = whiteToPlay ? 'w' : 'b';
14415     *p++ = ' ';
14416
14417   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14418     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14419   } else {
14420   if(nrCastlingRights) {
14421      q = p;
14422      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14423        /* [HGM] write directly from rights */
14424            if(boards[move][CASTLING][2] != NoRights &&
14425               boards[move][CASTLING][0] != NoRights   )
14426                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14427            if(boards[move][CASTLING][2] != NoRights &&
14428               boards[move][CASTLING][1] != NoRights   )
14429                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14430            if(boards[move][CASTLING][5] != NoRights &&
14431               boards[move][CASTLING][3] != NoRights   )
14432                 *p++ = boards[move][CASTLING][3] + AAA;
14433            if(boards[move][CASTLING][5] != NoRights &&
14434               boards[move][CASTLING][4] != NoRights   )
14435                 *p++ = boards[move][CASTLING][4] + AAA;
14436      } else {
14437
14438         /* [HGM] write true castling rights */
14439         if( nrCastlingRights == 6 ) {
14440             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14441                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14442             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14443                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14444             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14445                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14446             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14447                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14448         }
14449      }
14450      if (q == p) *p++ = '-'; /* No castling rights */
14451      *p++ = ' ';
14452   }
14453
14454   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14455      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14456     /* En passant target square */
14457     if (move > backwardMostMove) {
14458         fromX = moveList[move - 1][0] - AAA;
14459         fromY = moveList[move - 1][1] - ONE;
14460         toX = moveList[move - 1][2] - AAA;
14461         toY = moveList[move - 1][3] - ONE;
14462         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14463             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14464             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14465             fromX == toX) {
14466             /* 2-square pawn move just happened */
14467             *p++ = toX + AAA;
14468             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14469         } else {
14470             *p++ = '-';
14471         }
14472     } else if(move == backwardMostMove) {
14473         // [HGM] perhaps we should always do it like this, and forget the above?
14474         if((signed char)boards[move][EP_STATUS] >= 0) {
14475             *p++ = boards[move][EP_STATUS] + AAA;
14476             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14477         } else {
14478             *p++ = '-';
14479         }
14480     } else {
14481         *p++ = '-';
14482     }
14483     *p++ = ' ';
14484   }
14485   }
14486
14487     /* [HGM] find reversible plies */
14488     {   int i = 0, j=move;
14489
14490         if (appData.debugMode) { int k;
14491             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14492             for(k=backwardMostMove; k<=forwardMostMove; k++)
14493                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14494
14495         }
14496
14497         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14498         if( j == backwardMostMove ) i += initialRulePlies;
14499         sprintf(p, "%d ", i);
14500         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14501     }
14502     /* Fullmove number */
14503     sprintf(p, "%d", (move / 2) + 1);
14504     
14505     return StrSave(buf);
14506 }
14507
14508 Boolean
14509 ParseFEN(board, blackPlaysFirst, fen)
14510     Board board;
14511      int *blackPlaysFirst;
14512      char *fen;
14513 {
14514     int i, j;
14515     char *p;
14516     int emptycount;
14517     ChessSquare piece;
14518
14519     p = fen;
14520
14521     /* [HGM] by default clear Crazyhouse holdings, if present */
14522     if(gameInfo.holdingsWidth) {
14523        for(i=0; i<BOARD_HEIGHT; i++) {
14524            board[i][0]             = EmptySquare; /* black holdings */
14525            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14526            board[i][1]             = (ChessSquare) 0; /* black counts */
14527            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14528        }
14529     }
14530
14531     /* Piece placement data */
14532     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14533         j = 0;
14534         for (;;) {
14535             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14536                 if (*p == '/') p++;
14537                 emptycount = gameInfo.boardWidth - j;
14538                 while (emptycount--)
14539                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14540                 break;
14541 #if(BOARD_FILES >= 10)
14542             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14543                 p++; emptycount=10;
14544                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14545                 while (emptycount--)
14546                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14547 #endif
14548             } else if (isdigit(*p)) {
14549                 emptycount = *p++ - '0';
14550                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14551                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14552                 while (emptycount--)
14553                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14554             } else if (*p == '+' || isalpha(*p)) {
14555                 if (j >= gameInfo.boardWidth) return FALSE;
14556                 if(*p=='+') {
14557                     piece = CharToPiece(*++p);
14558                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14559                     piece = (ChessSquare) (PROMOTED piece ); p++;
14560                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14561                 } else piece = CharToPiece(*p++);
14562
14563                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14564                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14565                     piece = (ChessSquare) (PROMOTED piece);
14566                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14567                     p++;
14568                 }
14569                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14570             } else {
14571                 return FALSE;
14572             }
14573         }
14574     }
14575     while (*p == '/' || *p == ' ') p++;
14576
14577     /* [HGM] look for Crazyhouse holdings here */
14578     while(*p==' ') p++;
14579     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14580         if(*p == '[') p++;
14581         if(*p == '-' ) *p++; /* empty holdings */ else {
14582             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14583             /* if we would allow FEN reading to set board size, we would   */
14584             /* have to add holdings and shift the board read so far here   */
14585             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14586                 *p++;
14587                 if((int) piece >= (int) BlackPawn ) {
14588                     i = (int)piece - (int)BlackPawn;
14589                     i = PieceToNumber((ChessSquare)i);
14590                     if( i >= gameInfo.holdingsSize ) return FALSE;
14591                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14592                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14593                 } else {
14594                     i = (int)piece - (int)WhitePawn;
14595                     i = PieceToNumber((ChessSquare)i);
14596                     if( i >= gameInfo.holdingsSize ) return FALSE;
14597                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14598                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14599                 }
14600             }
14601         }
14602         if(*p == ']') *p++;
14603     }
14604
14605     while(*p == ' ') p++;
14606
14607     /* Active color */
14608     switch (*p++) {
14609       case 'w':
14610         *blackPlaysFirst = FALSE;
14611         break;
14612       case 'b': 
14613         *blackPlaysFirst = TRUE;
14614         break;
14615       default:
14616         return FALSE;
14617     }
14618
14619     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14620     /* return the extra info in global variiables             */
14621
14622     /* set defaults in case FEN is incomplete */
14623     board[EP_STATUS] = EP_UNKNOWN;
14624     for(i=0; i<nrCastlingRights; i++ ) {
14625         board[CASTLING][i] =
14626             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14627     }   /* assume possible unless obviously impossible */
14628     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14629     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14630     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14631                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14632     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14633     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14634     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14635                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14636     FENrulePlies = 0;
14637
14638     while(*p==' ') p++;
14639     if(nrCastlingRights) {
14640       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14641           /* castling indicator present, so default becomes no castlings */
14642           for(i=0; i<nrCastlingRights; i++ ) {
14643                  board[CASTLING][i] = NoRights;
14644           }
14645       }
14646       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14647              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14648              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14649              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14650         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14651
14652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14653             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14654             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14655         }
14656         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14657             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14658         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14659                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14660         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14661                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14662         switch(c) {
14663           case'K':
14664               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14665               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14666               board[CASTLING][2] = whiteKingFile;
14667               break;
14668           case'Q':
14669               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14670               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14671               board[CASTLING][2] = whiteKingFile;
14672               break;
14673           case'k':
14674               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14675               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14676               board[CASTLING][5] = blackKingFile;
14677               break;
14678           case'q':
14679               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14680               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14681               board[CASTLING][5] = blackKingFile;
14682           case '-':
14683               break;
14684           default: /* FRC castlings */
14685               if(c >= 'a') { /* black rights */
14686                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14687                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14688                   if(i == BOARD_RGHT) break;
14689                   board[CASTLING][5] = i;
14690                   c -= AAA;
14691                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14692                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14693                   if(c > i)
14694                       board[CASTLING][3] = c;
14695                   else
14696                       board[CASTLING][4] = c;
14697               } else { /* white rights */
14698                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14699                     if(board[0][i] == WhiteKing) break;
14700                   if(i == BOARD_RGHT) break;
14701                   board[CASTLING][2] = i;
14702                   c -= AAA - 'a' + 'A';
14703                   if(board[0][c] >= WhiteKing) break;
14704                   if(c > i)
14705                       board[CASTLING][0] = c;
14706                   else
14707                       board[CASTLING][1] = c;
14708               }
14709         }
14710       }
14711       for(i=0; i<nrCastlingRights; i++)
14712         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14713     if (appData.debugMode) {
14714         fprintf(debugFP, "FEN castling rights:");
14715         for(i=0; i<nrCastlingRights; i++)
14716         fprintf(debugFP, " %d", board[CASTLING][i]);
14717         fprintf(debugFP, "\n");
14718     }
14719
14720       while(*p==' ') p++;
14721     }
14722
14723     /* read e.p. field in games that know e.p. capture */
14724     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14725        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14726       if(*p=='-') {
14727         p++; board[EP_STATUS] = EP_NONE;
14728       } else {
14729          char c = *p++ - AAA;
14730
14731          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14732          if(*p >= '0' && *p <='9') *p++;
14733          board[EP_STATUS] = c;
14734       }
14735     }
14736
14737
14738     if(sscanf(p, "%d", &i) == 1) {
14739         FENrulePlies = i; /* 50-move ply counter */
14740         /* (The move number is still ignored)    */
14741     }
14742
14743     return TRUE;
14744 }
14745       
14746 void
14747 EditPositionPasteFEN(char *fen)
14748 {
14749   if (fen != NULL) {
14750     Board initial_position;
14751
14752     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14753       DisplayError(_("Bad FEN position in clipboard"), 0);
14754       return ;
14755     } else {
14756       int savedBlackPlaysFirst = blackPlaysFirst;
14757       EditPositionEvent();
14758       blackPlaysFirst = savedBlackPlaysFirst;
14759       CopyBoard(boards[0], initial_position);
14760       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14761       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14762       DisplayBothClocks();
14763       DrawPosition(FALSE, boards[currentMove]);
14764     }
14765   }
14766 }
14767
14768 static char cseq[12] = "\\   ";
14769
14770 Boolean set_cont_sequence(char *new_seq)
14771 {
14772     int len;
14773     Boolean ret;
14774
14775     // handle bad attempts to set the sequence
14776         if (!new_seq)
14777                 return 0; // acceptable error - no debug
14778
14779     len = strlen(new_seq);
14780     ret = (len > 0) && (len < sizeof(cseq));
14781     if (ret)
14782         strcpy(cseq, new_seq);
14783     else if (appData.debugMode)
14784         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14785     return ret;
14786 }
14787
14788 /*
14789     reformat a source message so words don't cross the width boundary.  internal
14790     newlines are not removed.  returns the wrapped size (no null character unless
14791     included in source message).  If dest is NULL, only calculate the size required
14792     for the dest buffer.  lp argument indicats line position upon entry, and it's
14793     passed back upon exit.
14794 */
14795 int wrap(char *dest, char *src, int count, int width, int *lp)
14796 {
14797     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14798
14799     cseq_len = strlen(cseq);
14800     old_line = line = *lp;
14801     ansi = len = clen = 0;
14802
14803     for (i=0; i < count; i++)
14804     {
14805         if (src[i] == '\033')
14806             ansi = 1;
14807
14808         // if we hit the width, back up
14809         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14810         {
14811             // store i & len in case the word is too long
14812             old_i = i, old_len = len;
14813
14814             // find the end of the last word
14815             while (i && src[i] != ' ' && src[i] != '\n')
14816             {
14817                 i--;
14818                 len--;
14819             }
14820
14821             // word too long?  restore i & len before splitting it
14822             if ((old_i-i+clen) >= width)
14823             {
14824                 i = old_i;
14825                 len = old_len;
14826             }
14827
14828             // extra space?
14829             if (i && src[i-1] == ' ')
14830                 len--;
14831
14832             if (src[i] != ' ' && src[i] != '\n')
14833             {
14834                 i--;
14835                 if (len)
14836                     len--;
14837             }
14838
14839             // now append the newline and continuation sequence
14840             if (dest)
14841                 dest[len] = '\n';
14842             len++;
14843             if (dest)
14844                 strncpy(dest+len, cseq, cseq_len);
14845             len += cseq_len;
14846             line = cseq_len;
14847             clen = cseq_len;
14848             continue;
14849         }
14850
14851         if (dest)
14852             dest[len] = src[i];
14853         len++;
14854         if (!ansi)
14855             line++;
14856         if (src[i] == '\n')
14857             line = 0;
14858         if (src[i] == 'm')
14859             ansi = 0;
14860     }
14861     if (dest && appData.debugMode)
14862     {
14863         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14864             count, width, line, len, *lp);
14865         show_bytes(debugFP, src, count);
14866         fprintf(debugFP, "\ndest: ");
14867         show_bytes(debugFP, dest, len);
14868         fprintf(debugFP, "\n");
14869     }
14870     *lp = dest ? line : old_line;
14871
14872     return len;
14873 }
14874
14875 // [HGM] vari: routines for shelving variations
14876
14877 void 
14878 PushTail(int firstMove, int lastMove)
14879 {
14880         int i, j, nrMoves = lastMove - firstMove;
14881
14882         if(appData.icsActive) { // only in local mode
14883                 forwardMostMove = currentMove; // mimic old ICS behavior
14884                 return;
14885         }
14886         if(storedGames >= MAX_VARIATIONS-1) return;
14887
14888         // push current tail of game on stack
14889         savedResult[storedGames] = gameInfo.result;
14890         savedDetails[storedGames] = gameInfo.resultDetails;
14891         gameInfo.resultDetails = NULL;
14892         savedFirst[storedGames] = firstMove;
14893         savedLast [storedGames] = lastMove;
14894         savedFramePtr[storedGames] = framePtr;
14895         framePtr -= nrMoves; // reserve space for the boards
14896         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14897             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14898             for(j=0; j<MOVE_LEN; j++)
14899                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14900             for(j=0; j<2*MOVE_LEN; j++)
14901                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14902             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14903             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14904             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14905             pvInfoList[firstMove+i-1].depth = 0;
14906             commentList[framePtr+i] = commentList[firstMove+i];
14907             commentList[firstMove+i] = NULL;
14908         }
14909
14910         storedGames++;
14911         forwardMostMove = currentMove; // truncte game so we can start variation
14912         if(storedGames == 1) GreyRevert(FALSE);
14913 }
14914
14915 Boolean
14916 PopTail(Boolean annotate)
14917 {
14918         int i, j, nrMoves;
14919         char buf[8000], moveBuf[20];
14920
14921         if(appData.icsActive) return FALSE; // only in local mode
14922         if(!storedGames) return FALSE; // sanity
14923
14924         storedGames--;
14925         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14926         nrMoves = savedLast[storedGames] - currentMove;
14927         if(annotate) {
14928                 int cnt = 10;
14929                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14930                 else strcpy(buf, "(");
14931                 for(i=currentMove; i<forwardMostMove; i++) {
14932                         if(WhiteOnMove(i))
14933                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14934                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14935                         strcat(buf, moveBuf);
14936                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14937                 }
14938                 strcat(buf, ")");
14939         }
14940         for(i=1; i<nrMoves; i++) { // copy last variation back
14941             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14942             for(j=0; j<MOVE_LEN; j++)
14943                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14944             for(j=0; j<2*MOVE_LEN; j++)
14945                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14946             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14947             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14948             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14949             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14950             commentList[currentMove+i] = commentList[framePtr+i];
14951             commentList[framePtr+i] = NULL;
14952         }
14953         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14954         framePtr = savedFramePtr[storedGames];
14955         gameInfo.result = savedResult[storedGames];
14956         if(gameInfo.resultDetails != NULL) {
14957             free(gameInfo.resultDetails);
14958       }
14959         gameInfo.resultDetails = savedDetails[storedGames];
14960         forwardMostMove = currentMove + nrMoves;
14961         if(storedGames == 0) GreyRevert(TRUE);
14962         return TRUE;
14963 }
14964
14965 void 
14966 CleanupTail()
14967 {       // remove all shelved variations
14968         int i;
14969         for(i=0; i<storedGames; i++) {
14970             if(savedDetails[i])
14971                 free(savedDetails[i]);
14972             savedDetails[i] = NULL;
14973         }
14974         for(i=framePtr; i<MAX_MOVES; i++) {
14975                 if(commentList[i]) free(commentList[i]);
14976                 commentList[i] = NULL;
14977         }
14978         framePtr = MAX_MOVES-1;
14979         storedGames = 0;
14980 }