Observe a game in the background while playing
[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             sprintf(buf, "play %d\n", seekNrList[closest]);
2241             if(click == Press) {
2242                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2243                 lastDown = closest;
2244                 return TRUE;
2245             } // on press 'hit', only show info
2246             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2247             SendToICS(ics_prefix);
2248             SendToICS(buf); // should this be "sought all"?
2249         } else if(click == Release) { // release 'miss' is ignored
2250             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2251             if(moving == 2) { // right up-click
2252                 nrOfSeekAds = 0; // refresh graph
2253                 soughtPending = TRUE;
2254                 SendToICS(ics_prefix);
2255                 SendToICS("sought\n"); // should this be "sought all"?
2256             }
2257             return TRUE;
2258         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2259         // press miss or release hit 'pop down' seek graph
2260         seekGraphUp = FALSE;
2261         DrawPosition(TRUE, NULL);
2262     }
2263     return TRUE;
2264 }
2265
2266 void
2267 read_from_ics(isr, closure, data, count, error)
2268      InputSourceRef isr;
2269      VOIDSTAR closure;
2270      char *data;
2271      int count;
2272      int error;
2273 {
2274 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2275 #define STARTED_NONE 0
2276 #define STARTED_MOVES 1
2277 #define STARTED_BOARD 2
2278 #define STARTED_OBSERVE 3
2279 #define STARTED_HOLDINGS 4
2280 #define STARTED_CHATTER 5
2281 #define STARTED_COMMENT 6
2282 #define STARTED_MOVES_NOHIDE 7
2283     
2284     static int started = STARTED_NONE;
2285     static char parse[20000];
2286     static int parse_pos = 0;
2287     static char buf[BUF_SIZE + 1];
2288     static int firstTime = TRUE, intfSet = FALSE;
2289     static ColorClass prevColor = ColorNormal;
2290     static int savingComment = FALSE;
2291     static int cmatch = 0; // continuation sequence match
2292     char *bp;
2293     char str[500];
2294     int i, oldi;
2295     int buf_len;
2296     int next_out;
2297     int tkind;
2298     int backup;    /* [DM] For zippy color lines */
2299     char *p;
2300     char talker[MSG_SIZ]; // [HGM] chat
2301     int channel;
2302
2303     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2304
2305     if (appData.debugMode) {
2306       if (!error) {
2307         fprintf(debugFP, "<ICS: ");
2308         show_bytes(debugFP, data, count);
2309         fprintf(debugFP, "\n");
2310       }
2311     }
2312
2313     if (appData.debugMode) { int f = forwardMostMove;
2314         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2315                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2316                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2317     }
2318     if (count > 0) {
2319         /* If last read ended with a partial line that we couldn't parse,
2320            prepend it to the new read and try again. */
2321         if (leftover_len > 0) {
2322             for (i=0; i<leftover_len; i++)
2323               buf[i] = buf[leftover_start + i];
2324         }
2325
2326     /* copy new characters into the buffer */
2327     bp = buf + leftover_len;
2328     buf_len=leftover_len;
2329     for (i=0; i<count; i++)
2330     {
2331         // ignore these
2332         if (data[i] == '\r')
2333             continue;
2334
2335         // join lines split by ICS?
2336         if (!appData.noJoin)
2337         {
2338             /*
2339                 Joining just consists of finding matches against the
2340                 continuation sequence, and discarding that sequence
2341                 if found instead of copying it.  So, until a match
2342                 fails, there's nothing to do since it might be the
2343                 complete sequence, and thus, something we don't want
2344                 copied.
2345             */
2346             if (data[i] == cont_seq[cmatch])
2347             {
2348                 cmatch++;
2349                 if (cmatch == strlen(cont_seq))
2350                 {
2351                     cmatch = 0; // complete match.  just reset the counter
2352
2353                     /*
2354                         it's possible for the ICS to not include the space
2355                         at the end of the last word, making our [correct]
2356                         join operation fuse two separate words.  the server
2357                         does this when the space occurs at the width setting.
2358                     */
2359                     if (!buf_len || buf[buf_len-1] != ' ')
2360                     {
2361                         *bp++ = ' ';
2362                         buf_len++;
2363                     }
2364                 }
2365                 continue;
2366             }
2367             else if (cmatch)
2368             {
2369                 /*
2370                     match failed, so we have to copy what matched before
2371                     falling through and copying this character.  In reality,
2372                     this will only ever be just the newline character, but
2373                     it doesn't hurt to be precise.
2374                 */
2375                 strncpy(bp, cont_seq, cmatch);
2376                 bp += cmatch;
2377                 buf_len += cmatch;
2378                 cmatch = 0;
2379             }
2380         }
2381
2382         // copy this char
2383         *bp++ = data[i];
2384         buf_len++;
2385     }
2386
2387         buf[buf_len] = NULLCHAR;
2388 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2389         next_out = 0;
2390         leftover_start = 0;
2391         
2392         i = 0;
2393         while (i < buf_len) {
2394             /* Deal with part of the TELNET option negotiation
2395                protocol.  We refuse to do anything beyond the
2396                defaults, except that we allow the WILL ECHO option,
2397                which ICS uses to turn off password echoing when we are
2398                directly connected to it.  We reject this option
2399                if localLineEditing mode is on (always on in xboard)
2400                and we are talking to port 23, which might be a real
2401                telnet server that will try to keep WILL ECHO on permanently.
2402              */
2403             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2404                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2405                 unsigned char option;
2406                 oldi = i;
2407                 switch ((unsigned char) buf[++i]) {
2408                   case TN_WILL:
2409                     if (appData.debugMode)
2410                       fprintf(debugFP, "\n<WILL ");
2411                     switch (option = (unsigned char) buf[++i]) {
2412                       case TN_ECHO:
2413                         if (appData.debugMode)
2414                           fprintf(debugFP, "ECHO ");
2415                         /* Reply only if this is a change, according
2416                            to the protocol rules. */
2417                         if (remoteEchoOption) break;
2418                         if (appData.localLineEditing &&
2419                             atoi(appData.icsPort) == TN_PORT) {
2420                             TelnetRequest(TN_DONT, TN_ECHO);
2421                         } else {
2422                             EchoOff();
2423                             TelnetRequest(TN_DO, TN_ECHO);
2424                             remoteEchoOption = TRUE;
2425                         }
2426                         break;
2427                       default:
2428                         if (appData.debugMode)
2429                           fprintf(debugFP, "%d ", option);
2430                         /* Whatever this is, we don't want it. */
2431                         TelnetRequest(TN_DONT, option);
2432                         break;
2433                     }
2434                     break;
2435                   case TN_WONT:
2436                     if (appData.debugMode)
2437                       fprintf(debugFP, "\n<WONT ");
2438                     switch (option = (unsigned char) buf[++i]) {
2439                       case TN_ECHO:
2440                         if (appData.debugMode)
2441                           fprintf(debugFP, "ECHO ");
2442                         /* Reply only if this is a change, according
2443                            to the protocol rules. */
2444                         if (!remoteEchoOption) break;
2445                         EchoOn();
2446                         TelnetRequest(TN_DONT, TN_ECHO);
2447                         remoteEchoOption = FALSE;
2448                         break;
2449                       default:
2450                         if (appData.debugMode)
2451                           fprintf(debugFP, "%d ", (unsigned char) option);
2452                         /* Whatever this is, it must already be turned
2453                            off, because we never agree to turn on
2454                            anything non-default, so according to the
2455                            protocol rules, we don't reply. */
2456                         break;
2457                     }
2458                     break;
2459                   case TN_DO:
2460                     if (appData.debugMode)
2461                       fprintf(debugFP, "\n<DO ");
2462                     switch (option = (unsigned char) buf[++i]) {
2463                       default:
2464                         /* Whatever this is, we refuse to do it. */
2465                         if (appData.debugMode)
2466                           fprintf(debugFP, "%d ", option);
2467                         TelnetRequest(TN_WONT, option);
2468                         break;
2469                     }
2470                     break;
2471                   case TN_DONT:
2472                     if (appData.debugMode)
2473                       fprintf(debugFP, "\n<DONT ");
2474                     switch (option = (unsigned char) buf[++i]) {
2475                       default:
2476                         if (appData.debugMode)
2477                           fprintf(debugFP, "%d ", option);
2478                         /* Whatever this is, we are already not doing
2479                            it, because we never agree to do anything
2480                            non-default, so according to the protocol
2481                            rules, we don't reply. */
2482                         break;
2483                     }
2484                     break;
2485                   case TN_IAC:
2486                     if (appData.debugMode)
2487                       fprintf(debugFP, "\n<IAC ");
2488                     /* Doubled IAC; pass it through */
2489                     i--;
2490                     break;
2491                   default:
2492                     if (appData.debugMode)
2493                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2494                     /* Drop all other telnet commands on the floor */
2495                     break;
2496                 }
2497                 if (oldi > next_out)
2498                   SendToPlayer(&buf[next_out], oldi - next_out);
2499                 if (++i > next_out)
2500                   next_out = i;
2501                 continue;
2502             }
2503                 
2504             /* OK, this at least will *usually* work */
2505             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2506                 loggedOn = TRUE;
2507             }
2508             
2509             if (loggedOn && !intfSet) {
2510                 if (ics_type == ICS_ICC) {
2511                   sprintf(str,
2512                           "/set-quietly interface %s\n/set-quietly style 12\n",
2513                           programVersion);
2514                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2515                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2516                 } else if (ics_type == ICS_CHESSNET) {
2517                   sprintf(str, "/style 12\n");
2518                 } else {
2519                   strcpy(str, "alias $ @\n$set interface ");
2520                   strcat(str, programVersion);
2521                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2522                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2523                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2524 #ifdef WIN32
2525                   strcat(str, "$iset nohighlight 1\n");
2526 #endif
2527                   strcat(str, "$iset lock 1\n$style 12\n");
2528                 }
2529                 SendToICS(str);
2530                 NotifyFrontendLogin();
2531                 intfSet = TRUE;
2532             }
2533
2534             if (started == STARTED_COMMENT) {
2535                 /* Accumulate characters in comment */
2536                 parse[parse_pos++] = buf[i];
2537                 if (buf[i] == '\n') {
2538                     parse[parse_pos] = NULLCHAR;
2539                     if(chattingPartner>=0) {
2540                         char mess[MSG_SIZ];
2541                         sprintf(mess, "%s%s", talker, parse);
2542                         OutputChatMessage(chattingPartner, mess);
2543                         chattingPartner = -1;
2544                     } else
2545                     if(!suppressKibitz) // [HGM] kibitz
2546                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2547                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2548                         int nrDigit = 0, nrAlph = 0, j;
2549                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2550                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2551                         parse[parse_pos] = NULLCHAR;
2552                         // try to be smart: if it does not look like search info, it should go to
2553                         // ICS interaction window after all, not to engine-output window.
2554                         for(j=0; j<parse_pos; j++) { // count letters and digits
2555                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2556                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2557                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2558                         }
2559                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2560                             int depth=0; float score;
2561                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2562                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2563                                 pvInfoList[forwardMostMove-1].depth = depth;
2564                                 pvInfoList[forwardMostMove-1].score = 100*score;
2565                             }
2566                             OutputKibitz(suppressKibitz, parse);
2567                             next_out = i+1; // [HGM] suppress printing in ICS window
2568                         } else {
2569                             char tmp[MSG_SIZ];
2570                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2571                             SendToPlayer(tmp, strlen(tmp));
2572                         }
2573                     }
2574                     started = STARTED_NONE;
2575                 } else {
2576                     /* Don't match patterns against characters in comment */
2577                     i++;
2578                     continue;
2579                 }
2580             }
2581             if (started == STARTED_CHATTER) {
2582                 if (buf[i] != '\n') {
2583                     /* Don't match patterns against characters in chatter */
2584                     i++;
2585                     continue;
2586                 }
2587                 started = STARTED_NONE;
2588             }
2589
2590             /* Kludge to deal with rcmd protocol */
2591             if (firstTime && looking_at(buf, &i, "\001*")) {
2592                 DisplayFatalError(&buf[1], 0, 1);
2593                 continue;
2594             } else {
2595                 firstTime = FALSE;
2596             }
2597
2598             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2599                 ics_type = ICS_ICC;
2600                 ics_prefix = "/";
2601                 if (appData.debugMode)
2602                   fprintf(debugFP, "ics_type %d\n", ics_type);
2603                 continue;
2604             }
2605             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2606                 ics_type = ICS_FICS;
2607                 ics_prefix = "$";
2608                 if (appData.debugMode)
2609                   fprintf(debugFP, "ics_type %d\n", ics_type);
2610                 continue;
2611             }
2612             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2613                 ics_type = ICS_CHESSNET;
2614                 ics_prefix = "/";
2615                 if (appData.debugMode)
2616                   fprintf(debugFP, "ics_type %d\n", ics_type);
2617                 continue;
2618             }
2619
2620             if (!loggedOn &&
2621                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2622                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2623                  looking_at(buf, &i, "will be \"*\""))) {
2624               strcpy(ics_handle, star_match[0]);
2625               continue;
2626             }
2627
2628             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2629               char buf[MSG_SIZ];
2630               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2631               DisplayIcsInteractionTitle(buf);
2632               have_set_title = TRUE;
2633             }
2634
2635             /* skip finger notes */
2636             if (started == STARTED_NONE &&
2637                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2638                  (buf[i] == '1' && buf[i+1] == '0')) &&
2639                 buf[i+2] == ':' && buf[i+3] == ' ') {
2640               started = STARTED_CHATTER;
2641               i += 3;
2642               continue;
2643             }
2644
2645             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2646             if(appData.seekGraph) {
2647                 if(soughtPending && MatchSoughtLine(buf+i)) {
2648                     i = strstr(buf+i, "rated") - buf;
2649                     next_out = leftover_start = i;
2650                     started = STARTED_CHATTER;
2651                     suppressKibitz = TRUE;
2652                     continue;
2653                 }
2654                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2655                         && looking_at(buf, &i, "* ads displayed")) {
2656                     soughtPending = FALSE;
2657                     seekGraphUp = TRUE;
2658                     DrawSeekGraph();
2659                     continue;
2660                 }
2661                 if(appData.autoRefresh) {
2662                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2663                         int s = (ics_type == ICS_ICC); // ICC format differs
2664                         if(seekGraphUp)
2665                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2666                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2667                         looking_at(buf, &i, "*% "); // eat prompt
2668                         next_out = i; // suppress
2669                         continue;
2670                     }
2671                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2672                         char *p = star_match[0];
2673                         while(*p) {
2674                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2675                             while(*p && *p++ != ' '); // next
2676                         }
2677                         looking_at(buf, &i, "*% "); // eat prompt
2678                         next_out = i;
2679                         continue;
2680                     }
2681                 }
2682             }
2683
2684             /* skip formula vars */
2685             if (started == STARTED_NONE &&
2686                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2687               started = STARTED_CHATTER;
2688               i += 3;
2689               continue;
2690             }
2691
2692             oldi = i;
2693             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2694             if (appData.autoKibitz && started == STARTED_NONE && 
2695                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2696                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2697                 if(looking_at(buf, &i, "* kibitzes: ") &&
2698                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2699                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2700                         suppressKibitz = TRUE;
2701                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2702                                 && (gameMode == IcsPlayingWhite)) ||
2703                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2704                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2705                             started = STARTED_CHATTER; // own kibitz we simply discard
2706                         else {
2707                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2708                             parse_pos = 0; parse[0] = NULLCHAR;
2709                             savingComment = TRUE;
2710                             suppressKibitz = gameMode != IcsObserving ? 2 :
2711                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2712                         } 
2713                         continue;
2714                 } else
2715                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2716                     // suppress the acknowledgements of our own autoKibitz
2717                     char *p;
2718                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2719                     SendToPlayer(star_match[0], strlen(star_match[0]));
2720                     looking_at(buf, &i, "*% "); // eat prompt
2721                     next_out = i;
2722                 }
2723             } // [HGM] kibitz: end of patch
2724
2725 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2726
2727             // [HGM] chat: intercept tells by users for which we have an open chat window
2728             channel = -1;
2729             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2730                                            looking_at(buf, &i, "* whispers:") ||
2731                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2732                                            looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2733                 int p;
2734                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2735                 chattingPartner = -1;
2736
2737                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2738                 for(p=0; p<MAX_CHAT; p++) {
2739                     if(channel == atoi(chatPartner[p])) {
2740                     talker[0] = '['; strcat(talker, "] ");
2741                     chattingPartner = p; break;
2742                     }
2743                 } else
2744                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2745                 for(p=0; p<MAX_CHAT; p++) {
2746                     if(!strcmp("WHISPER", chatPartner[p])) {
2747                         talker[0] = '['; strcat(talker, "] ");
2748                         chattingPartner = p; break;
2749                     }
2750                 }
2751                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2752                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2753                     talker[0] = 0;
2754                     chattingPartner = p; break;
2755                 }
2756                 if(chattingPartner<0) i = oldi; else {
2757                     started = STARTED_COMMENT;
2758                     parse_pos = 0; parse[0] = NULLCHAR;
2759                     savingComment = 3 + chattingPartner; // counts as TRUE
2760                     suppressKibitz = TRUE;
2761                 }
2762             } // [HGM] chat: end of patch
2763
2764             if (appData.zippyTalk || appData.zippyPlay) {
2765                 /* [DM] Backup address for color zippy lines */
2766                 backup = i;
2767 #if ZIPPY
2768        #ifdef WIN32
2769                if (loggedOn == TRUE)
2770                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2771                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2772        #else
2773                 if (ZippyControl(buf, &i) ||
2774                     ZippyConverse(buf, &i) ||
2775                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2776                       loggedOn = TRUE;
2777                       if (!appData.colorize) continue;
2778                 }
2779        #endif
2780 #endif
2781             } // [DM] 'else { ' deleted
2782                 if (
2783                     /* Regular tells and says */
2784                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2785                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2786                     looking_at(buf, &i, "* says: ") ||
2787                     /* Don't color "message" or "messages" output */
2788                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2789                     looking_at(buf, &i, "*. * at *:*: ") ||
2790                     looking_at(buf, &i, "--* (*:*): ") ||
2791                     /* Message notifications (same color as tells) */
2792                     looking_at(buf, &i, "* has left a message ") ||
2793                     looking_at(buf, &i, "* just sent you a message:\n") ||
2794                     /* Whispers and kibitzes */
2795                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2796                     looking_at(buf, &i, "* kibitzes: ") ||
2797                     /* Channel tells */
2798                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2799
2800                   if (tkind == 1 && strchr(star_match[0], ':')) {
2801                       /* Avoid "tells you:" spoofs in channels */
2802                      tkind = 3;
2803                   }
2804                   if (star_match[0][0] == NULLCHAR ||
2805                       strchr(star_match[0], ' ') ||
2806                       (tkind == 3 && strchr(star_match[1], ' '))) {
2807                     /* Reject bogus matches */
2808                     i = oldi;
2809                   } else {
2810                     if (appData.colorize) {
2811                       if (oldi > next_out) {
2812                         SendToPlayer(&buf[next_out], oldi - next_out);
2813                         next_out = oldi;
2814                       }
2815                       switch (tkind) {
2816                       case 1:
2817                         Colorize(ColorTell, FALSE);
2818                         curColor = ColorTell;
2819                         break;
2820                       case 2:
2821                         Colorize(ColorKibitz, FALSE);
2822                         curColor = ColorKibitz;
2823                         break;
2824                       case 3:
2825                         p = strrchr(star_match[1], '(');
2826                         if (p == NULL) {
2827                           p = star_match[1];
2828                         } else {
2829                           p++;
2830                         }
2831                         if (atoi(p) == 1) {
2832                           Colorize(ColorChannel1, FALSE);
2833                           curColor = ColorChannel1;
2834                         } else {
2835                           Colorize(ColorChannel, FALSE);
2836                           curColor = ColorChannel;
2837                         }
2838                         break;
2839                       case 5:
2840                         curColor = ColorNormal;
2841                         break;
2842                       }
2843                     }
2844                     if (started == STARTED_NONE && appData.autoComment &&
2845                         (gameMode == IcsObserving ||
2846                          gameMode == IcsPlayingWhite ||
2847                          gameMode == IcsPlayingBlack)) {
2848                       parse_pos = i - oldi;
2849                       memcpy(parse, &buf[oldi], parse_pos);
2850                       parse[parse_pos] = NULLCHAR;
2851                       started = STARTED_COMMENT;
2852                       savingComment = TRUE;
2853                     } else {
2854                       started = STARTED_CHATTER;
2855                       savingComment = FALSE;
2856                     }
2857                     loggedOn = TRUE;
2858                     continue;
2859                   }
2860                 }
2861
2862                 if (looking_at(buf, &i, "* s-shouts: ") ||
2863                     looking_at(buf, &i, "* c-shouts: ")) {
2864                     if (appData.colorize) {
2865                         if (oldi > next_out) {
2866                             SendToPlayer(&buf[next_out], oldi - next_out);
2867                             next_out = oldi;
2868                         }
2869                         Colorize(ColorSShout, FALSE);
2870                         curColor = ColorSShout;
2871                     }
2872                     loggedOn = TRUE;
2873                     started = STARTED_CHATTER;
2874                     continue;
2875                 }
2876
2877                 if (looking_at(buf, &i, "--->")) {
2878                     loggedOn = TRUE;
2879                     continue;
2880                 }
2881
2882                 if (looking_at(buf, &i, "* shouts: ") ||
2883                     looking_at(buf, &i, "--> ")) {
2884                     if (appData.colorize) {
2885                         if (oldi > next_out) {
2886                             SendToPlayer(&buf[next_out], oldi - next_out);
2887                             next_out = oldi;
2888                         }
2889                         Colorize(ColorShout, FALSE);
2890                         curColor = ColorShout;
2891                     }
2892                     loggedOn = TRUE;
2893                     started = STARTED_CHATTER;
2894                     continue;
2895                 }
2896
2897                 if (looking_at( buf, &i, "Challenge:")) {
2898                     if (appData.colorize) {
2899                         if (oldi > next_out) {
2900                             SendToPlayer(&buf[next_out], oldi - next_out);
2901                             next_out = oldi;
2902                         }
2903                         Colorize(ColorChallenge, FALSE);
2904                         curColor = ColorChallenge;
2905                     }
2906                     loggedOn = TRUE;
2907                     continue;
2908                 }
2909
2910                 if (looking_at(buf, &i, "* offers you") ||
2911                     looking_at(buf, &i, "* offers to be") ||
2912                     looking_at(buf, &i, "* would like to") ||
2913                     looking_at(buf, &i, "* requests to") ||
2914                     looking_at(buf, &i, "Your opponent offers") ||
2915                     looking_at(buf, &i, "Your opponent requests")) {
2916
2917                     if (appData.colorize) {
2918                         if (oldi > next_out) {
2919                             SendToPlayer(&buf[next_out], oldi - next_out);
2920                             next_out = oldi;
2921                         }
2922                         Colorize(ColorRequest, FALSE);
2923                         curColor = ColorRequest;
2924                     }
2925                     continue;
2926                 }
2927
2928                 if (looking_at(buf, &i, "* (*) seeking")) {
2929                     if (appData.colorize) {
2930                         if (oldi > next_out) {
2931                             SendToPlayer(&buf[next_out], oldi - next_out);
2932                             next_out = oldi;
2933                         }
2934                         Colorize(ColorSeek, FALSE);
2935                         curColor = ColorSeek;
2936                     }
2937                     continue;
2938             }
2939
2940             if (looking_at(buf, &i, "\\   ")) {
2941                 if (prevColor != ColorNormal) {
2942                     if (oldi > next_out) {
2943                         SendToPlayer(&buf[next_out], oldi - next_out);
2944                         next_out = oldi;
2945                     }
2946                     Colorize(prevColor, TRUE);
2947                     curColor = prevColor;
2948                 }
2949                 if (savingComment) {
2950                     parse_pos = i - oldi;
2951                     memcpy(parse, &buf[oldi], parse_pos);
2952                     parse[parse_pos] = NULLCHAR;
2953                     started = STARTED_COMMENT;
2954                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2955                         chattingPartner = savingComment - 3; // kludge to remember the box
2956                 } else {
2957                     started = STARTED_CHATTER;
2958                 }
2959                 continue;
2960             }
2961
2962             if (looking_at(buf, &i, "Black Strength :") ||
2963                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2964                 looking_at(buf, &i, "<10>") ||
2965                 looking_at(buf, &i, "#@#")) {
2966                 /* Wrong board style */
2967                 loggedOn = TRUE;
2968                 SendToICS(ics_prefix);
2969                 SendToICS("set style 12\n");
2970                 SendToICS(ics_prefix);
2971                 SendToICS("refresh\n");
2972                 continue;
2973             }
2974             
2975             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2976                 ICSInitScript();
2977                 have_sent_ICS_logon = 1;
2978                 continue;
2979             }
2980               
2981             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2982                 (looking_at(buf, &i, "\n<12> ") ||
2983                  looking_at(buf, &i, "<12> "))) {
2984                 loggedOn = TRUE;
2985                 if (oldi > next_out) {
2986                     SendToPlayer(&buf[next_out], oldi - next_out);
2987                 }
2988                 next_out = i;
2989                 started = STARTED_BOARD;
2990                 parse_pos = 0;
2991                 continue;
2992             }
2993
2994             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2995                 looking_at(buf, &i, "<b1> ")) {
2996                 if (oldi > next_out) {
2997                     SendToPlayer(&buf[next_out], oldi - next_out);
2998                 }
2999                 next_out = i;
3000                 started = STARTED_HOLDINGS;
3001                 parse_pos = 0;
3002                 continue;
3003             }
3004
3005             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3006                 loggedOn = TRUE;
3007                 /* Header for a move list -- first line */
3008
3009                 switch (ics_getting_history) {
3010                   case H_FALSE:
3011                     switch (gameMode) {
3012                       case IcsIdle:
3013                       case BeginningOfGame:
3014                         /* User typed "moves" or "oldmoves" while we
3015                            were idle.  Pretend we asked for these
3016                            moves and soak them up so user can step
3017                            through them and/or save them.
3018                            */
3019                         Reset(FALSE, TRUE);
3020                         gameMode = IcsObserving;
3021                         ModeHighlight();
3022                         ics_gamenum = -1;
3023                         ics_getting_history = H_GOT_UNREQ_HEADER;
3024                         break;
3025                       case EditGame: /*?*/
3026                       case EditPosition: /*?*/
3027                         /* Should above feature work in these modes too? */
3028                         /* For now it doesn't */
3029                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3030                         break;
3031                       default:
3032                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3033                         break;
3034                     }
3035                     break;
3036                   case H_REQUESTED:
3037                     /* Is this the right one? */
3038                     if (gameInfo.white && gameInfo.black &&
3039                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3040                         strcmp(gameInfo.black, star_match[2]) == 0) {
3041                         /* All is well */
3042                         ics_getting_history = H_GOT_REQ_HEADER;
3043                     }
3044                     break;
3045                   case H_GOT_REQ_HEADER:
3046                   case H_GOT_UNREQ_HEADER:
3047                   case H_GOT_UNWANTED_HEADER:
3048                   case H_GETTING_MOVES:
3049                     /* Should not happen */
3050                     DisplayError(_("Error gathering move list: two headers"), 0);
3051                     ics_getting_history = H_FALSE;
3052                     break;
3053                 }
3054
3055                 /* Save player ratings into gameInfo if needed */
3056                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3057                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3058                     (gameInfo.whiteRating == -1 ||
3059                      gameInfo.blackRating == -1)) {
3060
3061                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3062                     gameInfo.blackRating = string_to_rating(star_match[3]);
3063                     if (appData.debugMode)
3064                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3065                               gameInfo.whiteRating, gameInfo.blackRating);
3066                 }
3067                 continue;
3068             }
3069
3070             if (looking_at(buf, &i,
3071               "* * match, initial time: * minute*, increment: * second")) {
3072                 /* Header for a move list -- second line */
3073                 /* Initial board will follow if this is a wild game */
3074                 if (gameInfo.event != NULL) free(gameInfo.event);
3075                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3076                 gameInfo.event = StrSave(str);
3077                 /* [HGM] we switched variant. Translate boards if needed. */
3078                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3079                 continue;
3080             }
3081
3082             if (looking_at(buf, &i, "Move  ")) {
3083                 /* Beginning of a move list */
3084                 switch (ics_getting_history) {
3085                   case H_FALSE:
3086                     /* Normally should not happen */
3087                     /* Maybe user hit reset while we were parsing */
3088                     break;
3089                   case H_REQUESTED:
3090                     /* Happens if we are ignoring a move list that is not
3091                      * the one we just requested.  Common if the user
3092                      * tries to observe two games without turning off
3093                      * getMoveList */
3094                     break;
3095                   case H_GETTING_MOVES:
3096                     /* Should not happen */
3097                     DisplayError(_("Error gathering move list: nested"), 0);
3098                     ics_getting_history = H_FALSE;
3099                     break;
3100                   case H_GOT_REQ_HEADER:
3101                     ics_getting_history = H_GETTING_MOVES;
3102                     started = STARTED_MOVES;
3103                     parse_pos = 0;
3104                     if (oldi > next_out) {
3105                         SendToPlayer(&buf[next_out], oldi - next_out);
3106                     }
3107                     break;
3108                   case H_GOT_UNREQ_HEADER:
3109                     ics_getting_history = H_GETTING_MOVES;
3110                     started = STARTED_MOVES_NOHIDE;
3111                     parse_pos = 0;
3112                     break;
3113                   case H_GOT_UNWANTED_HEADER:
3114                     ics_getting_history = H_FALSE;
3115                     break;
3116                 }
3117                 continue;
3118             }                           
3119             
3120             if (looking_at(buf, &i, "% ") ||
3121                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3122                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3123                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3124                     soughtPending = FALSE;
3125                     seekGraphUp = TRUE;
3126                     DrawSeekGraph();
3127                 }
3128                 if(suppressKibitz) next_out = i;
3129                 savingComment = FALSE;
3130                 suppressKibitz = 0;
3131                 switch (started) {
3132                   case STARTED_MOVES:
3133                   case STARTED_MOVES_NOHIDE:
3134                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3135                     parse[parse_pos + i - oldi] = NULLCHAR;
3136                     ParseGameHistory(parse);
3137 #if ZIPPY
3138                     if (appData.zippyPlay && first.initDone) {
3139                         FeedMovesToProgram(&first, forwardMostMove);
3140                         if (gameMode == IcsPlayingWhite) {
3141                             if (WhiteOnMove(forwardMostMove)) {
3142                                 if (first.sendTime) {
3143                                   if (first.useColors) {
3144                                     SendToProgram("black\n", &first); 
3145                                   }
3146                                   SendTimeRemaining(&first, TRUE);
3147                                 }
3148                                 if (first.useColors) {
3149                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3150                                 }
3151                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3152                                 first.maybeThinking = TRUE;
3153                             } else {
3154                                 if (first.usePlayother) {
3155                                   if (first.sendTime) {
3156                                     SendTimeRemaining(&first, TRUE);
3157                                   }
3158                                   SendToProgram("playother\n", &first);
3159                                   firstMove = FALSE;
3160                                 } else {
3161                                   firstMove = TRUE;
3162                                 }
3163                             }
3164                         } else if (gameMode == IcsPlayingBlack) {
3165                             if (!WhiteOnMove(forwardMostMove)) {
3166                                 if (first.sendTime) {
3167                                   if (first.useColors) {
3168                                     SendToProgram("white\n", &first);
3169                                   }
3170                                   SendTimeRemaining(&first, FALSE);
3171                                 }
3172                                 if (first.useColors) {
3173                                   SendToProgram("black\n", &first);
3174                                 }
3175                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3176                                 first.maybeThinking = TRUE;
3177                             } else {
3178                                 if (first.usePlayother) {
3179                                   if (first.sendTime) {
3180                                     SendTimeRemaining(&first, FALSE);
3181                                   }
3182                                   SendToProgram("playother\n", &first);
3183                                   firstMove = FALSE;
3184                                 } else {
3185                                   firstMove = TRUE;
3186                                 }
3187                             }
3188                         }                       
3189                     }
3190 #endif
3191                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3192                         /* Moves came from oldmoves or moves command
3193                            while we weren't doing anything else.
3194                            */
3195                         currentMove = forwardMostMove;
3196                         ClearHighlights();/*!!could figure this out*/
3197                         flipView = appData.flipView;
3198                         DrawPosition(TRUE, boards[currentMove]);
3199                         DisplayBothClocks();
3200                         sprintf(str, "%s vs. %s",
3201                                 gameInfo.white, gameInfo.black);
3202                         DisplayTitle(str);
3203                         gameMode = IcsIdle;
3204                     } else {
3205                         /* Moves were history of an active game */
3206                         if (gameInfo.resultDetails != NULL) {
3207                             free(gameInfo.resultDetails);
3208                             gameInfo.resultDetails = NULL;
3209                         }
3210                     }
3211                     HistorySet(parseList, backwardMostMove,
3212                                forwardMostMove, currentMove-1);
3213                     DisplayMove(currentMove - 1);
3214                     if (started == STARTED_MOVES) next_out = i;
3215                     started = STARTED_NONE;
3216                     ics_getting_history = H_FALSE;
3217                     break;
3218
3219                   case STARTED_OBSERVE:
3220                     started = STARTED_NONE;
3221                     SendToICS(ics_prefix);
3222                     SendToICS("refresh\n");
3223                     break;
3224
3225                   default:
3226                     break;
3227                 }
3228                 if(bookHit) { // [HGM] book: simulate book reply
3229                     static char bookMove[MSG_SIZ]; // a bit generous?
3230
3231                     programStats.nodes = programStats.depth = programStats.time = 
3232                     programStats.score = programStats.got_only_move = 0;
3233                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3234
3235                     strcpy(bookMove, "move ");
3236                     strcat(bookMove, bookHit);
3237                     HandleMachineMove(bookMove, &first);
3238                 }
3239                 continue;
3240             }
3241             
3242             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3243                  started == STARTED_HOLDINGS ||
3244                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3245                 /* Accumulate characters in move list or board */
3246                 parse[parse_pos++] = buf[i];
3247             }
3248             
3249             /* Start of game messages.  Mostly we detect start of game
3250                when the first board image arrives.  On some versions
3251                of the ICS, though, we need to do a "refresh" after starting
3252                to observe in order to get the current board right away. */
3253             if (looking_at(buf, &i, "Adding game * to observation list")) {
3254                 started = STARTED_OBSERVE;
3255                 continue;
3256             }
3257
3258             /* Handle auto-observe */
3259             if (appData.autoObserve &&
3260                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3261                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3262                 char *player;
3263                 /* Choose the player that was highlighted, if any. */
3264                 if (star_match[0][0] == '\033' ||
3265                     star_match[1][0] != '\033') {
3266                     player = star_match[0];
3267                 } else {
3268                     player = star_match[2];
3269                 }
3270                 sprintf(str, "%sobserve %s\n",
3271                         ics_prefix, StripHighlightAndTitle(player));
3272                 SendToICS(str);
3273
3274                 /* Save ratings from notify string */
3275                 strcpy(player1Name, star_match[0]);
3276                 player1Rating = string_to_rating(star_match[1]);
3277                 strcpy(player2Name, star_match[2]);
3278                 player2Rating = string_to_rating(star_match[3]);
3279
3280                 if (appData.debugMode)
3281                   fprintf(debugFP, 
3282                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3283                           player1Name, player1Rating,
3284                           player2Name, player2Rating);
3285
3286                 continue;
3287             }
3288
3289             /* Deal with automatic examine mode after a game,
3290                and with IcsObserving -> IcsExamining transition */
3291             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3292                 looking_at(buf, &i, "has made you an examiner of game *")) {
3293
3294                 int gamenum = atoi(star_match[0]);
3295                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3296                     gamenum == ics_gamenum) {
3297                     /* We were already playing or observing this game;
3298                        no need to refetch history */
3299                     gameMode = IcsExamining;
3300                     if (pausing) {
3301                         pauseExamForwardMostMove = forwardMostMove;
3302                     } else if (currentMove < forwardMostMove) {
3303                         ForwardInner(forwardMostMove);
3304                     }
3305                 } else {
3306                     /* I don't think this case really can happen */
3307                     SendToICS(ics_prefix);
3308                     SendToICS("refresh\n");
3309                 }
3310                 continue;
3311             }    
3312             
3313             /* Error messages */
3314 //          if (ics_user_moved) {
3315             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3316                 if (looking_at(buf, &i, "Illegal move") ||
3317                     looking_at(buf, &i, "Not a legal move") ||
3318                     looking_at(buf, &i, "Your king is in check") ||
3319                     looking_at(buf, &i, "It isn't your turn") ||
3320                     looking_at(buf, &i, "It is not your move")) {
3321                     /* Illegal move */
3322                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3323                         currentMove = forwardMostMove-1;
3324                         DisplayMove(currentMove - 1); /* before DMError */
3325                         DrawPosition(FALSE, boards[currentMove]);
3326                         SwitchClocks(forwardMostMove-1); // [HGM] race
3327                         DisplayBothClocks();
3328                     }
3329                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3330                     ics_user_moved = 0;
3331                     continue;
3332                 }
3333             }
3334
3335             if (looking_at(buf, &i, "still have time") ||
3336                 looking_at(buf, &i, "not out of time") ||
3337                 looking_at(buf, &i, "either player is out of time") ||
3338                 looking_at(buf, &i, "has timeseal; checking")) {
3339                 /* We must have called his flag a little too soon */
3340                 whiteFlag = blackFlag = FALSE;
3341                 continue;
3342             }
3343
3344             if (looking_at(buf, &i, "added * seconds to") ||
3345                 looking_at(buf, &i, "seconds were added to")) {
3346                 /* Update the clocks */
3347                 SendToICS(ics_prefix);
3348                 SendToICS("refresh\n");
3349                 continue;
3350             }
3351
3352             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3353                 ics_clock_paused = TRUE;
3354                 StopClocks();
3355                 continue;
3356             }
3357
3358             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3359                 ics_clock_paused = FALSE;
3360                 StartClocks();
3361                 continue;
3362             }
3363
3364             /* Grab player ratings from the Creating: message.
3365                Note we have to check for the special case when
3366                the ICS inserts things like [white] or [black]. */
3367             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3368                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3369                 /* star_matches:
3370                    0    player 1 name (not necessarily white)
3371                    1    player 1 rating
3372                    2    empty, white, or black (IGNORED)
3373                    3    player 2 name (not necessarily black)
3374                    4    player 2 rating
3375                    
3376                    The names/ratings are sorted out when the game
3377                    actually starts (below).
3378                 */
3379                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3380                 player1Rating = string_to_rating(star_match[1]);
3381                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3382                 player2Rating = string_to_rating(star_match[4]);
3383
3384                 if (appData.debugMode)
3385                   fprintf(debugFP, 
3386                           "Ratings from 'Creating:' %s %d, %s %d\n",
3387                           player1Name, player1Rating,
3388                           player2Name, player2Rating);
3389
3390                 continue;
3391             }
3392             
3393             /* Improved generic start/end-of-game messages */
3394             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3395                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3396                 /* If tkind == 0: */
3397                 /* star_match[0] is the game number */
3398                 /*           [1] is the white player's name */
3399                 /*           [2] is the black player's name */
3400                 /* For end-of-game: */
3401                 /*           [3] is the reason for the game end */
3402                 /*           [4] is a PGN end game-token, preceded by " " */
3403                 /* For start-of-game: */
3404                 /*           [3] begins with "Creating" or "Continuing" */
3405                 /*           [4] is " *" or empty (don't care). */
3406                 int gamenum = atoi(star_match[0]);
3407                 char *whitename, *blackname, *why, *endtoken;
3408                 ChessMove endtype = (ChessMove) 0;
3409
3410                 if (tkind == 0) {
3411                   whitename = star_match[1];
3412                   blackname = star_match[2];
3413                   why = star_match[3];
3414                   endtoken = star_match[4];
3415                 } else {
3416                   whitename = star_match[1];
3417                   blackname = star_match[3];
3418                   why = star_match[5];
3419                   endtoken = star_match[6];
3420                 }
3421
3422                 /* Game start messages */
3423                 if (strncmp(why, "Creating ", 9) == 0 ||
3424                     strncmp(why, "Continuing ", 11) == 0) {
3425                     gs_gamenum = gamenum;
3426                     strcpy(gs_kind, strchr(why, ' ') + 1);
3427                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3428 #if ZIPPY
3429                     if (appData.zippyPlay) {
3430                         ZippyGameStart(whitename, blackname);
3431                     }
3432 #endif /*ZIPPY*/
3433                     continue;
3434                 }
3435
3436                 /* Game end messages */
3437                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3438                     ics_gamenum != gamenum) {
3439                     continue;
3440                 }
3441                 while (endtoken[0] == ' ') endtoken++;
3442                 switch (endtoken[0]) {
3443                   case '*':
3444                   default:
3445                     endtype = GameUnfinished;
3446                     break;
3447                   case '0':
3448                     endtype = BlackWins;
3449                     break;
3450                   case '1':
3451                     if (endtoken[1] == '/')
3452                       endtype = GameIsDrawn;
3453                     else
3454                       endtype = WhiteWins;
3455                     break;
3456                 }
3457                 GameEnds(endtype, why, GE_ICS);
3458 #if ZIPPY
3459                 if (appData.zippyPlay && first.initDone) {
3460                     ZippyGameEnd(endtype, why);
3461                     if (first.pr == NULL) {
3462                       /* Start the next process early so that we'll
3463                          be ready for the next challenge */
3464                       StartChessProgram(&first);
3465                     }
3466                     /* Send "new" early, in case this command takes
3467                        a long time to finish, so that we'll be ready
3468                        for the next challenge. */
3469                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3470                     Reset(TRUE, TRUE);
3471                 }
3472 #endif /*ZIPPY*/
3473                 continue;
3474             }
3475
3476             if (looking_at(buf, &i, "Removing game * from observation") ||
3477                 looking_at(buf, &i, "no longer observing game *") ||
3478                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3479                 if (gameMode == IcsObserving &&
3480                     atoi(star_match[0]) == ics_gamenum)
3481                   {
3482                       /* icsEngineAnalyze */
3483                       if (appData.icsEngineAnalyze) {
3484                             ExitAnalyzeMode();
3485                             ModeHighlight();
3486                       }
3487                       StopClocks();
3488                       gameMode = IcsIdle;
3489                       ics_gamenum = -1;
3490                       ics_user_moved = FALSE;
3491                   }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "no longer examining game *")) {
3496                 if (gameMode == IcsExamining &&
3497                     atoi(star_match[0]) == ics_gamenum)
3498                   {
3499                       gameMode = IcsIdle;
3500                       ics_gamenum = -1;
3501                       ics_user_moved = FALSE;
3502                   }
3503                 continue;
3504             }
3505
3506             /* Advance leftover_start past any newlines we find,
3507                so only partial lines can get reparsed */
3508             if (looking_at(buf, &i, "\n")) {
3509                 prevColor = curColor;
3510                 if (curColor != ColorNormal) {
3511                     if (oldi > next_out) {
3512                         SendToPlayer(&buf[next_out], oldi - next_out);
3513                         next_out = oldi;
3514                     }
3515                     Colorize(ColorNormal, FALSE);
3516                     curColor = ColorNormal;
3517                 }
3518                 if (started == STARTED_BOARD) {
3519                     started = STARTED_NONE;
3520                     parse[parse_pos] = NULLCHAR;
3521                     ParseBoard12(parse);
3522                     ics_user_moved = 0;
3523
3524                     /* Send premove here */
3525                     if (appData.premove) {
3526                       char str[MSG_SIZ];
3527                       if (currentMove == 0 &&
3528                           gameMode == IcsPlayingWhite &&
3529                           appData.premoveWhite) {
3530                         sprintf(str, "%s\n", appData.premoveWhiteText);
3531                         if (appData.debugMode)
3532                           fprintf(debugFP, "Sending premove:\n");
3533                         SendToICS(str);
3534                       } else if (currentMove == 1 &&
3535                                  gameMode == IcsPlayingBlack &&
3536                                  appData.premoveBlack) {
3537                         sprintf(str, "%s\n", appData.premoveBlackText);
3538                         if (appData.debugMode)
3539                           fprintf(debugFP, "Sending premove:\n");
3540                         SendToICS(str);
3541                       } else if (gotPremove) {
3542                         gotPremove = 0;
3543                         ClearPremoveHighlights();
3544                         if (appData.debugMode)
3545                           fprintf(debugFP, "Sending premove:\n");
3546                           UserMoveEvent(premoveFromX, premoveFromY, 
3547                                         premoveToX, premoveToY, 
3548                                         premovePromoChar);
3549                       }
3550                     }
3551
3552                     /* Usually suppress following prompt */
3553                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3554                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3555                         if (looking_at(buf, &i, "*% ")) {
3556                             savingComment = FALSE;
3557                             suppressKibitz = 0;
3558                         }
3559                     }
3560                     next_out = i;
3561                 } else if (started == STARTED_HOLDINGS) {
3562                     int gamenum;
3563                     char new_piece[MSG_SIZ];
3564                     started = STARTED_NONE;
3565                     parse[parse_pos] = NULLCHAR;
3566                     if (appData.debugMode)
3567                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3568                                                         parse, currentMove);
3569                     if (sscanf(parse, " game %d", &gamenum) == 1 &&
3570                         gamenum == ics_gamenum) {
3571                         if (gameInfo.variant == VariantNormal) {
3572                           /* [HGM] We seem to switch variant during a game!
3573                            * Presumably no holdings were displayed, so we have
3574                            * to move the position two files to the right to
3575                            * create room for them!
3576                            */
3577                           VariantClass newVariant;
3578                           switch(gameInfo.boardWidth) { // base guess on board width
3579                                 case 9:  newVariant = VariantShogi; break;
3580                                 case 10: newVariant = VariantGreat; break;
3581                                 default: newVariant = VariantCrazyhouse; break;
3582                           }
3583                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3584                           /* Get a move list just to see the header, which
3585                              will tell us whether this is really bug or zh */
3586                           if (ics_getting_history == H_FALSE) {
3587                             ics_getting_history = H_REQUESTED;
3588                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3589                             SendToICS(str);
3590                           }
3591                         }
3592                         new_piece[0] = NULLCHAR;
3593                         sscanf(parse, "game %d white [%s black [%s <- %s",
3594                                &gamenum, white_holding, black_holding,
3595                                new_piece);
3596                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3597                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3598                         /* [HGM] copy holdings to board holdings area */
3599                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3600                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3601                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3602 #if ZIPPY
3603                         if (appData.zippyPlay && first.initDone) {
3604                             ZippyHoldings(white_holding, black_holding,
3605                                           new_piece);
3606                         }
3607 #endif /*ZIPPY*/
3608                         if (tinyLayout || smallLayout) {
3609                             char wh[16], bh[16];
3610                             PackHolding(wh, white_holding);
3611                             PackHolding(bh, black_holding);
3612                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3613                                     gameInfo.white, gameInfo.black);
3614                         } else {
3615                             sprintf(str, "%s [%s] vs. %s [%s]",
3616                                     gameInfo.white, white_holding,
3617                                     gameInfo.black, black_holding);
3618                         }
3619
3620                         DrawPosition(FALSE, boards[currentMove]);
3621                         DisplayTitle(str);
3622                     }
3623                     /* Suppress following prompt */
3624                     if (looking_at(buf, &i, "*% ")) {
3625                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3626                         savingComment = FALSE;
3627                         suppressKibitz = 0;
3628                     }
3629                     next_out = i;
3630                 }
3631                 continue;
3632             }
3633
3634             i++;                /* skip unparsed character and loop back */
3635         }
3636         
3637         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3638 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3639 //          SendToPlayer(&buf[next_out], i - next_out);
3640             started != STARTED_HOLDINGS && leftover_start > next_out) {
3641             SendToPlayer(&buf[next_out], leftover_start - next_out);
3642             next_out = i;
3643         }
3644         
3645         leftover_len = buf_len - leftover_start;
3646         /* if buffer ends with something we couldn't parse,
3647            reparse it after appending the next read */
3648         
3649     } else if (count == 0) {
3650         RemoveInputSource(isr);
3651         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3652     } else {
3653         DisplayFatalError(_("Error reading from ICS"), error, 1);
3654     }
3655 }
3656
3657
3658 /* Board style 12 looks like this:
3659    
3660    <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
3661    
3662  * The "<12> " is stripped before it gets to this routine.  The two
3663  * trailing 0's (flip state and clock ticking) are later addition, and
3664  * some chess servers may not have them, or may have only the first.
3665  * Additional trailing fields may be added in the future.  
3666  */
3667
3668 #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"
3669
3670 #define RELATION_OBSERVING_PLAYED    0
3671 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3672 #define RELATION_PLAYING_MYMOVE      1
3673 #define RELATION_PLAYING_NOTMYMOVE  -1
3674 #define RELATION_EXAMINING           2
3675 #define RELATION_ISOLATED_BOARD     -3
3676 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3677
3678 void
3679 ParseBoard12(string)
3680      char *string;
3681
3682     GameMode newGameMode;
3683     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3684     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3685     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3686     char to_play, board_chars[200];
3687     char move_str[500], str[500], elapsed_time[500];
3688     char black[32], white[32];
3689     Board board;
3690     int prevMove = currentMove;
3691     int ticking = 2;
3692     ChessMove moveType;
3693     int fromX, fromY, toX, toY;
3694     char promoChar;
3695     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3696     char *bookHit = NULL; // [HGM] book
3697     Boolean weird = FALSE, reqFlag = FALSE;
3698
3699     fromX = fromY = toX = toY = -1;
3700     
3701     newGame = FALSE;
3702
3703     if (appData.debugMode)
3704       fprintf(debugFP, _("Parsing board: %s\n"), string);
3705
3706     move_str[0] = NULLCHAR;
3707     elapsed_time[0] = NULLCHAR;
3708     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3709         int  i = 0, j;
3710         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3711             if(string[i] == ' ') { ranks++; files = 0; }
3712             else files++;
3713             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3714             i++;
3715         }
3716         for(j = 0; j <i; j++) board_chars[j] = string[j];
3717         board_chars[i] = '\0';
3718         string += i + 1;
3719     }
3720     n = sscanf(string, PATTERN, &to_play, &double_push,
3721                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3722                &gamenum, white, black, &relation, &basetime, &increment,
3723                &white_stren, &black_stren, &white_time, &black_time,
3724                &moveNum, str, elapsed_time, move_str, &ics_flip,
3725                &ticking);
3726
3727     if (n < 21) {
3728         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3729         DisplayError(str, 0);
3730         return;
3731     }
3732
3733     /* Convert the move number to internal form */
3734     moveNum = (moveNum - 1) * 2;
3735     if (to_play == 'B') moveNum++;
3736     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3737       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3738                         0, 1);
3739       return;
3740     }
3741     
3742     switch (relation) {
3743       case RELATION_OBSERVING_PLAYED:
3744       case RELATION_OBSERVING_STATIC:
3745         if (gamenum == -1) {
3746             /* Old ICC buglet */
3747             relation = RELATION_OBSERVING_STATIC;
3748         }
3749         newGameMode = IcsObserving;
3750         break;
3751       case RELATION_PLAYING_MYMOVE:
3752       case RELATION_PLAYING_NOTMYMOVE:
3753         newGameMode =
3754           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3755             IcsPlayingWhite : IcsPlayingBlack;
3756         break;
3757       case RELATION_EXAMINING:
3758         newGameMode = IcsExamining;
3759         break;
3760       case RELATION_ISOLATED_BOARD:
3761       default:
3762         /* Just display this board.  If user was doing something else,
3763            we will forget about it until the next board comes. */ 
3764         newGameMode = IcsIdle;
3765         break;
3766       case RELATION_STARTING_POSITION:
3767         newGameMode = gameMode;
3768         break;
3769     }
3770     
3771     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3772          && newGameMode == IcsObserving && appData.bgObserve) {
3773       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3774       char buf[MSG_SIZ];
3775       for (k = 0; k < ranks; k++) {
3776         for (j = 0; j < files; j++)
3777           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3778         if(gameInfo.holdingsWidth > 1) {
3779              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3780              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3781         }
3782       }
3783       CopyBoard(partnerBoard, board);
3784       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3785       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3786                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3787       DisplayMessage(buf, "");
3788       return;
3789     }
3790
3791     /* Modify behavior for initial board display on move listing
3792        of wild games.
3793        */
3794     switch (ics_getting_history) {
3795       case H_FALSE:
3796       case H_REQUESTED:
3797         break;
3798       case H_GOT_REQ_HEADER:
3799       case H_GOT_UNREQ_HEADER:
3800         /* This is the initial position of the current game */
3801         gamenum = ics_gamenum;
3802         moveNum = 0;            /* old ICS bug workaround */
3803         if (to_play == 'B') {
3804           startedFromSetupPosition = TRUE;
3805           blackPlaysFirst = TRUE;
3806           moveNum = 1;
3807           if (forwardMostMove == 0) forwardMostMove = 1;
3808           if (backwardMostMove == 0) backwardMostMove = 1;
3809           if (currentMove == 0) currentMove = 1;
3810         }
3811         newGameMode = gameMode;
3812         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3813         break;
3814       case H_GOT_UNWANTED_HEADER:
3815         /* This is an initial board that we don't want */
3816         return;
3817       case H_GETTING_MOVES:
3818         /* Should not happen */
3819         DisplayError(_("Error gathering move list: extra board"), 0);
3820         ics_getting_history = H_FALSE;
3821         return;
3822     }
3823
3824    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3825                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3826      /* [HGM] We seem to have switched variant unexpectedly
3827       * Try to guess new variant from board size
3828       */
3829           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3830           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3831           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3832           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3833           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3834           if(!weird) newVariant = VariantNormal;
3835           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3836           /* Get a move list just to see the header, which
3837              will tell us whether this is really bug or zh */
3838           if (ics_getting_history == H_FALSE) {
3839             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3840             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3841             SendToICS(str);
3842           }
3843     }
3844     
3845     /* Take action if this is the first board of a new game, or of a
3846        different game than is currently being displayed.  */
3847     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3848         relation == RELATION_ISOLATED_BOARD) {
3849         
3850         /* Forget the old game and get the history (if any) of the new one */
3851         if (gameMode != BeginningOfGame) {
3852           Reset(TRUE, TRUE);
3853         }
3854         newGame = TRUE;
3855         if (appData.autoRaiseBoard) BoardToTop();
3856         prevMove = -3;
3857         if (gamenum == -1) {
3858             newGameMode = IcsIdle;
3859         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3860                    appData.getMoveList && !reqFlag) {
3861             /* Need to get game history */
3862             ics_getting_history = H_REQUESTED;
3863             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3864             SendToICS(str);
3865         }
3866         
3867         /* Initially flip the board to have black on the bottom if playing
3868            black or if the ICS flip flag is set, but let the user change
3869            it with the Flip View button. */
3870         flipView = appData.autoFlipView ? 
3871           (newGameMode == IcsPlayingBlack) || ics_flip :
3872           appData.flipView;
3873         
3874         /* Done with values from previous mode; copy in new ones */
3875         gameMode = newGameMode;
3876         ModeHighlight();
3877         ics_gamenum = gamenum;
3878         if (gamenum == gs_gamenum) {
3879             int klen = strlen(gs_kind);
3880             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3881             sprintf(str, "ICS %s", gs_kind);
3882             gameInfo.event = StrSave(str);
3883         } else {
3884             gameInfo.event = StrSave("ICS game");
3885         }
3886         gameInfo.site = StrSave(appData.icsHost);
3887         gameInfo.date = PGNDate();
3888         gameInfo.round = StrSave("-");
3889         gameInfo.white = StrSave(white);
3890         gameInfo.black = StrSave(black);
3891         timeControl = basetime * 60 * 1000;
3892         timeControl_2 = 0;
3893         timeIncrement = increment * 1000;
3894         movesPerSession = 0;
3895         gameInfo.timeControl = TimeControlTagValue();
3896         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3897   if (appData.debugMode) {
3898     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3899     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3900     setbuf(debugFP, NULL);
3901   }
3902
3903         gameInfo.outOfBook = NULL;
3904         
3905         /* Do we have the ratings? */
3906         if (strcmp(player1Name, white) == 0 &&
3907             strcmp(player2Name, black) == 0) {
3908             if (appData.debugMode)
3909               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3910                       player1Rating, player2Rating);
3911             gameInfo.whiteRating = player1Rating;
3912             gameInfo.blackRating = player2Rating;
3913         } else if (strcmp(player2Name, white) == 0 &&
3914                    strcmp(player1Name, black) == 0) {
3915             if (appData.debugMode)
3916               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3917                       player2Rating, player1Rating);
3918             gameInfo.whiteRating = player2Rating;
3919             gameInfo.blackRating = player1Rating;
3920         }
3921         player1Name[0] = player2Name[0] = NULLCHAR;
3922
3923         /* Silence shouts if requested */
3924         if (appData.quietPlay &&
3925             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3926             SendToICS(ics_prefix);
3927             SendToICS("set shout 0\n");
3928         }
3929     }
3930     
3931     /* Deal with midgame name changes */
3932     if (!newGame) {
3933         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3934             if (gameInfo.white) free(gameInfo.white);
3935             gameInfo.white = StrSave(white);
3936         }
3937         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3938             if (gameInfo.black) free(gameInfo.black);
3939             gameInfo.black = StrSave(black);
3940         }
3941     }
3942     
3943     /* Throw away game result if anything actually changes in examine mode */
3944     if (gameMode == IcsExamining && !newGame) {
3945         gameInfo.result = GameUnfinished;
3946         if (gameInfo.resultDetails != NULL) {
3947             free(gameInfo.resultDetails);
3948             gameInfo.resultDetails = NULL;
3949         }
3950     }
3951     
3952     /* In pausing && IcsExamining mode, we ignore boards coming
3953        in if they are in a different variation than we are. */
3954     if (pauseExamInvalid) return;
3955     if (pausing && gameMode == IcsExamining) {
3956         if (moveNum <= pauseExamForwardMostMove) {
3957             pauseExamInvalid = TRUE;
3958             forwardMostMove = pauseExamForwardMostMove;
3959             return;
3960         }
3961     }
3962     
3963   if (appData.debugMode) {
3964     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3965   }
3966     /* Parse the board */
3967     for (k = 0; k < ranks; k++) {
3968       for (j = 0; j < files; j++)
3969         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3970       if(gameInfo.holdingsWidth > 1) {
3971            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3972            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3973       }
3974     }
3975     CopyBoard(boards[moveNum], board);
3976     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3977     if (moveNum == 0) {
3978         startedFromSetupPosition =
3979           !CompareBoards(board, initialPosition);
3980         if(startedFromSetupPosition)
3981             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3982     }
3983
3984     /* [HGM] Set castling rights. Take the outermost Rooks,
3985        to make it also work for FRC opening positions. Note that board12
3986        is really defective for later FRC positions, as it has no way to
3987        indicate which Rook can castle if they are on the same side of King.
3988        For the initial position we grant rights to the outermost Rooks,
3989        and remember thos rights, and we then copy them on positions
3990        later in an FRC game. This means WB might not recognize castlings with
3991        Rooks that have moved back to their original position as illegal,
3992        but in ICS mode that is not its job anyway.
3993     */
3994     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3995     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3996
3997         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3998             if(board[0][i] == WhiteRook) j = i;
3999         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4000         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4001             if(board[0][i] == WhiteRook) j = i;
4002         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4003         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4004             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4005         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4006         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4007             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4008         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4009
4010         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4011         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4012             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4013         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4014             if(board[BOARD_HEIGHT-1][k] == bKing)
4015                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4016         if(gameInfo.variant == VariantTwoKings) {
4017             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4018             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4019             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4020         }
4021     } else { int r;
4022         r = boards[moveNum][CASTLING][0] = initialRights[0];
4023         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4024         r = boards[moveNum][CASTLING][1] = initialRights[1];
4025         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4026         r = boards[moveNum][CASTLING][3] = initialRights[3];
4027         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4028         r = boards[moveNum][CASTLING][4] = initialRights[4];
4029         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4030         /* wildcastle kludge: always assume King has rights */
4031         r = boards[moveNum][CASTLING][2] = initialRights[2];
4032         r = boards[moveNum][CASTLING][5] = initialRights[5];
4033     }
4034     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4035     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4036
4037     
4038     if (ics_getting_history == H_GOT_REQ_HEADER ||
4039         ics_getting_history == H_GOT_UNREQ_HEADER) {
4040         /* This was an initial position from a move list, not
4041            the current position */
4042         return;
4043     }
4044     
4045     /* Update currentMove and known move number limits */
4046     newMove = newGame || moveNum > forwardMostMove;
4047
4048     if (newGame) {
4049         forwardMostMove = backwardMostMove = currentMove = moveNum;
4050         if (gameMode == IcsExamining && moveNum == 0) {
4051           /* Workaround for ICS limitation: we are not told the wild
4052              type when starting to examine a game.  But if we ask for
4053              the move list, the move list header will tell us */
4054             ics_getting_history = H_REQUESTED;
4055             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4056             SendToICS(str);
4057         }
4058     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4059                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4060 #if ZIPPY
4061         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4062         /* [HGM] applied this also to an engine that is silently watching        */
4063         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4064             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4065             gameInfo.variant == currentlyInitializedVariant) {
4066           takeback = forwardMostMove - moveNum;
4067           for (i = 0; i < takeback; i++) {
4068             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4069             SendToProgram("undo\n", &first);
4070           }
4071         }
4072 #endif
4073
4074         forwardMostMove = moveNum;
4075         if (!pausing || currentMove > forwardMostMove)
4076           currentMove = forwardMostMove;
4077     } else {
4078         /* New part of history that is not contiguous with old part */ 
4079         if (pausing && gameMode == IcsExamining) {
4080             pauseExamInvalid = TRUE;
4081             forwardMostMove = pauseExamForwardMostMove;
4082             return;
4083         }
4084         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4085 #if ZIPPY
4086             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4087                 // [HGM] when we will receive the move list we now request, it will be
4088                 // fed to the engine from the first move on. So if the engine is not
4089                 // in the initial position now, bring it there.
4090                 InitChessProgram(&first, 0);
4091             }
4092 #endif
4093             ics_getting_history = H_REQUESTED;
4094             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4095             SendToICS(str);
4096         }
4097         forwardMostMove = backwardMostMove = currentMove = moveNum;
4098     }
4099     
4100     /* Update the clocks */
4101     if (strchr(elapsed_time, '.')) {
4102       /* Time is in ms */
4103       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4104       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4105     } else {
4106       /* Time is in seconds */
4107       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4108       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4109     }
4110       
4111
4112 #if ZIPPY
4113     if (appData.zippyPlay && newGame &&
4114         gameMode != IcsObserving && gameMode != IcsIdle &&
4115         gameMode != IcsExamining)
4116       ZippyFirstBoard(moveNum, basetime, increment);
4117 #endif
4118     
4119     /* Put the move on the move list, first converting
4120        to canonical algebraic form. */
4121     if (moveNum > 0) {
4122   if (appData.debugMode) {
4123     if (appData.debugMode) { int f = forwardMostMove;
4124         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4125                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4126                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4127     }
4128     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4129     fprintf(debugFP, "moveNum = %d\n", moveNum);
4130     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4131     setbuf(debugFP, NULL);
4132   }
4133         if (moveNum <= backwardMostMove) {
4134             /* We don't know what the board looked like before
4135                this move.  Punt. */
4136             strcpy(parseList[moveNum - 1], move_str);
4137             strcat(parseList[moveNum - 1], " ");
4138             strcat(parseList[moveNum - 1], elapsed_time);
4139             moveList[moveNum - 1][0] = NULLCHAR;
4140         } else if (strcmp(move_str, "none") == 0) {
4141             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4142             /* Again, we don't know what the board looked like;
4143                this is really the start of the game. */
4144             parseList[moveNum - 1][0] = NULLCHAR;
4145             moveList[moveNum - 1][0] = NULLCHAR;
4146             backwardMostMove = moveNum;
4147             startedFromSetupPosition = TRUE;
4148             fromX = fromY = toX = toY = -1;
4149         } else {
4150           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4151           //                 So we parse the long-algebraic move string in stead of the SAN move
4152           int valid; char buf[MSG_SIZ], *prom;
4153
4154           // str looks something like "Q/a1-a2"; kill the slash
4155           if(str[1] == '/') 
4156                 sprintf(buf, "%c%s", str[0], str+2);
4157           else  strcpy(buf, str); // might be castling
4158           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4159                 strcat(buf, prom); // long move lacks promo specification!
4160           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4161                 if(appData.debugMode) 
4162                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4163                 strcpy(move_str, buf);
4164           }
4165           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4166                                 &fromX, &fromY, &toX, &toY, &promoChar)
4167                || ParseOneMove(buf, moveNum - 1, &moveType,
4168                                 &fromX, &fromY, &toX, &toY, &promoChar);
4169           // end of long SAN patch
4170           if (valid) {
4171             (void) CoordsToAlgebraic(boards[moveNum - 1],
4172                                      PosFlags(moveNum - 1),
4173                                      fromY, fromX, toY, toX, promoChar,
4174                                      parseList[moveNum-1]);
4175             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4176               case MT_NONE:
4177               case MT_STALEMATE:
4178               default:
4179                 break;
4180               case MT_CHECK:
4181                 if(gameInfo.variant != VariantShogi)
4182                     strcat(parseList[moveNum - 1], "+");
4183                 break;
4184               case MT_CHECKMATE:
4185               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4186                 strcat(parseList[moveNum - 1], "#");
4187                 break;
4188             }
4189             strcat(parseList[moveNum - 1], " ");
4190             strcat(parseList[moveNum - 1], elapsed_time);
4191             /* currentMoveString is set as a side-effect of ParseOneMove */
4192             strcpy(moveList[moveNum - 1], currentMoveString);
4193             strcat(moveList[moveNum - 1], "\n");
4194           } else {
4195             /* Move from ICS was illegal!?  Punt. */
4196   if (appData.debugMode) {
4197     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4198     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4199   }
4200             strcpy(parseList[moveNum - 1], move_str);
4201             strcat(parseList[moveNum - 1], " ");
4202             strcat(parseList[moveNum - 1], elapsed_time);
4203             moveList[moveNum - 1][0] = NULLCHAR;
4204             fromX = fromY = toX = toY = -1;
4205           }
4206         }
4207   if (appData.debugMode) {
4208     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4209     setbuf(debugFP, NULL);
4210   }
4211
4212 #if ZIPPY
4213         /* Send move to chess program (BEFORE animating it). */
4214         if (appData.zippyPlay && !newGame && newMove && 
4215            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4216
4217             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4218                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4219                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4220                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4221                             move_str);
4222                     DisplayError(str, 0);
4223                 } else {
4224                     if (first.sendTime) {
4225                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4226                     }
4227                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4228                     if (firstMove && !bookHit) {
4229                         firstMove = FALSE;
4230                         if (first.useColors) {
4231                           SendToProgram(gameMode == IcsPlayingWhite ?
4232                                         "white\ngo\n" :
4233                                         "black\ngo\n", &first);
4234                         } else {
4235                           SendToProgram("go\n", &first);
4236                         }
4237                         first.maybeThinking = TRUE;
4238                     }
4239                 }
4240             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4241               if (moveList[moveNum - 1][0] == NULLCHAR) {
4242                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4243                 DisplayError(str, 0);
4244               } else {
4245                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4246                 SendMoveToProgram(moveNum - 1, &first);
4247               }
4248             }
4249         }
4250 #endif
4251     }
4252
4253     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4254         /* If move comes from a remote source, animate it.  If it
4255            isn't remote, it will have already been animated. */
4256         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4257             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4258         }
4259         if (!pausing && appData.highlightLastMove) {
4260             SetHighlights(fromX, fromY, toX, toY);
4261         }
4262     }
4263     
4264     /* Start the clocks */
4265     whiteFlag = blackFlag = FALSE;
4266     appData.clockMode = !(basetime == 0 && increment == 0);
4267     if (ticking == 0) {
4268       ics_clock_paused = TRUE;
4269       StopClocks();
4270     } else if (ticking == 1) {
4271       ics_clock_paused = FALSE;
4272     }
4273     if (gameMode == IcsIdle ||
4274         relation == RELATION_OBSERVING_STATIC ||
4275         relation == RELATION_EXAMINING ||
4276         ics_clock_paused)
4277       DisplayBothClocks();
4278     else
4279       StartClocks();
4280     
4281     /* Display opponents and material strengths */
4282     if (gameInfo.variant != VariantBughouse &&
4283         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4284         if (tinyLayout || smallLayout) {
4285             if(gameInfo.variant == VariantNormal)
4286                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4287                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4288                     basetime, increment);
4289             else
4290                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4291                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4292                     basetime, increment, (int) gameInfo.variant);
4293         } else {
4294             if(gameInfo.variant == VariantNormal)
4295                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4296                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4297                     basetime, increment);
4298             else
4299                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4300                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4301                     basetime, increment, VariantName(gameInfo.variant));
4302         }
4303         DisplayTitle(str);
4304   if (appData.debugMode) {
4305     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4306   }
4307     }
4308
4309
4310     /* Display the board */
4311     if (!pausing && !appData.noGUI) {
4312       
4313       if (appData.premove)
4314           if (!gotPremove || 
4315              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4316              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4317               ClearPremoveHighlights();
4318
4319       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4320       DrawPosition(j, boards[currentMove]);
4321
4322       DisplayMove(moveNum - 1);
4323       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4324             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4325               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4326         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4327       }
4328     }
4329
4330     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4331 #if ZIPPY
4332     if(bookHit) { // [HGM] book: simulate book reply
4333         static char bookMove[MSG_SIZ]; // a bit generous?
4334
4335         programStats.nodes = programStats.depth = programStats.time = 
4336         programStats.score = programStats.got_only_move = 0;
4337         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4338
4339         strcpy(bookMove, "move ");
4340         strcat(bookMove, bookHit);
4341         HandleMachineMove(bookMove, &first);
4342     }
4343 #endif
4344 }
4345
4346 void
4347 GetMoveListEvent()
4348 {
4349     char buf[MSG_SIZ];
4350     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4351         ics_getting_history = H_REQUESTED;
4352         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4353         SendToICS(buf);
4354     }
4355 }
4356
4357 void
4358 AnalysisPeriodicEvent(force)
4359      int force;
4360 {
4361     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4362          && !force) || !appData.periodicUpdates)
4363       return;
4364
4365     /* Send . command to Crafty to collect stats */
4366     SendToProgram(".\n", &first);
4367
4368     /* Don't send another until we get a response (this makes
4369        us stop sending to old Crafty's which don't understand
4370        the "." command (sending illegal cmds resets node count & time,
4371        which looks bad)) */
4372     programStats.ok_to_send = 0;
4373 }
4374
4375 void ics_update_width(new_width)
4376         int new_width;
4377 {
4378         ics_printf("set width %d\n", new_width);
4379 }
4380
4381 void
4382 SendMoveToProgram(moveNum, cps)
4383      int moveNum;
4384      ChessProgramState *cps;
4385 {
4386     char buf[MSG_SIZ];
4387
4388     if (cps->useUsermove) {
4389       SendToProgram("usermove ", cps);
4390     }
4391     if (cps->useSAN) {
4392       char *space;
4393       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4394         int len = space - parseList[moveNum];
4395         memcpy(buf, parseList[moveNum], len);
4396         buf[len++] = '\n';
4397         buf[len] = NULLCHAR;
4398       } else {
4399         sprintf(buf, "%s\n", parseList[moveNum]);
4400       }
4401       SendToProgram(buf, cps);
4402     } else {
4403       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4404         AlphaRank(moveList[moveNum], 4);
4405         SendToProgram(moveList[moveNum], cps);
4406         AlphaRank(moveList[moveNum], 4); // and back
4407       } else
4408       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4409        * the engine. It would be nice to have a better way to identify castle 
4410        * moves here. */
4411       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4412                                                                          && cps->useOOCastle) {
4413         int fromX = moveList[moveNum][0] - AAA; 
4414         int fromY = moveList[moveNum][1] - ONE;
4415         int toX = moveList[moveNum][2] - AAA; 
4416         int toY = moveList[moveNum][3] - ONE;
4417         if((boards[moveNum][fromY][fromX] == WhiteKing 
4418             && boards[moveNum][toY][toX] == WhiteRook)
4419            || (boards[moveNum][fromY][fromX] == BlackKing 
4420                && boards[moveNum][toY][toX] == BlackRook)) {
4421           if(toX > fromX) SendToProgram("O-O\n", cps);
4422           else SendToProgram("O-O-O\n", cps);
4423         }
4424         else SendToProgram(moveList[moveNum], cps);
4425       }
4426       else SendToProgram(moveList[moveNum], cps);
4427       /* End of additions by Tord */
4428     }
4429
4430     /* [HGM] setting up the opening has brought engine in force mode! */
4431     /*       Send 'go' if we are in a mode where machine should play. */
4432     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4433         (gameMode == TwoMachinesPlay   ||
4434 #ifdef ZIPPY
4435          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4436 #endif
4437          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4438         SendToProgram("go\n", cps);
4439   if (appData.debugMode) {
4440     fprintf(debugFP, "(extra)\n");
4441   }
4442     }
4443     setboardSpoiledMachineBlack = 0;
4444 }
4445
4446 void
4447 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4448      ChessMove moveType;
4449      int fromX, fromY, toX, toY;
4450 {
4451     char user_move[MSG_SIZ];
4452
4453     switch (moveType) {
4454       default:
4455         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4456                 (int)moveType, fromX, fromY, toX, toY);
4457         DisplayError(user_move + strlen("say "), 0);
4458         break;
4459       case WhiteKingSideCastle:
4460       case BlackKingSideCastle:
4461       case WhiteQueenSideCastleWild:
4462       case BlackQueenSideCastleWild:
4463       /* PUSH Fabien */
4464       case WhiteHSideCastleFR:
4465       case BlackHSideCastleFR:
4466       /* POP Fabien */
4467         sprintf(user_move, "o-o\n");
4468         break;
4469       case WhiteQueenSideCastle:
4470       case BlackQueenSideCastle:
4471       case WhiteKingSideCastleWild:
4472       case BlackKingSideCastleWild:
4473       /* PUSH Fabien */
4474       case WhiteASideCastleFR:
4475       case BlackASideCastleFR:
4476       /* POP Fabien */
4477         sprintf(user_move, "o-o-o\n");
4478         break;
4479       case WhitePromotionQueen:
4480       case BlackPromotionQueen:
4481       case WhitePromotionRook:
4482       case BlackPromotionRook:
4483       case WhitePromotionBishop:
4484       case BlackPromotionBishop:
4485       case WhitePromotionKnight:
4486       case BlackPromotionKnight:
4487       case WhitePromotionKing:
4488       case BlackPromotionKing:
4489       case WhitePromotionChancellor:
4490       case BlackPromotionChancellor:
4491       case WhitePromotionArchbishop:
4492       case BlackPromotionArchbishop:
4493         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4494             sprintf(user_move, "%c%c%c%c=%c\n",
4495                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4496                 PieceToChar(WhiteFerz));
4497         else if(gameInfo.variant == VariantGreat)
4498             sprintf(user_move, "%c%c%c%c=%c\n",
4499                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4500                 PieceToChar(WhiteMan));
4501         else
4502             sprintf(user_move, "%c%c%c%c=%c\n",
4503                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4504                 PieceToChar(PromoPiece(moveType)));
4505         break;
4506       case WhiteDrop:
4507       case BlackDrop:
4508         sprintf(user_move, "%c@%c%c\n",
4509                 ToUpper(PieceToChar((ChessSquare) fromX)),
4510                 AAA + toX, ONE + toY);
4511         break;
4512       case NormalMove:
4513       case WhiteCapturesEnPassant:
4514       case BlackCapturesEnPassant:
4515       case IllegalMove:  /* could be a variant we don't quite understand */
4516         sprintf(user_move, "%c%c%c%c\n",
4517                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4518         break;
4519     }
4520     SendToICS(user_move);
4521     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4522         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4523 }
4524
4525 void
4526 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4527      int rf, ff, rt, ft;
4528      char promoChar;
4529      char move[7];
4530 {
4531     if (rf == DROP_RANK) {
4532         sprintf(move, "%c@%c%c\n",
4533                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4534     } else {
4535         if (promoChar == 'x' || promoChar == NULLCHAR) {
4536             sprintf(move, "%c%c%c%c\n",
4537                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4538         } else {
4539             sprintf(move, "%c%c%c%c%c\n",
4540                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4541         }
4542     }
4543 }
4544
4545 void
4546 ProcessICSInitScript(f)
4547      FILE *f;
4548 {
4549     char buf[MSG_SIZ];
4550
4551     while (fgets(buf, MSG_SIZ, f)) {
4552         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4553     }
4554
4555     fclose(f);
4556 }
4557
4558
4559 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4560 void
4561 AlphaRank(char *move, int n)
4562 {
4563 //    char *p = move, c; int x, y;
4564
4565     if (appData.debugMode) {
4566         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4567     }
4568
4569     if(move[1]=='*' && 
4570        move[2]>='0' && move[2]<='9' &&
4571        move[3]>='a' && move[3]<='x'    ) {
4572         move[1] = '@';
4573         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4574         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4575     } else
4576     if(move[0]>='0' && move[0]<='9' &&
4577        move[1]>='a' && move[1]<='x' &&
4578        move[2]>='0' && move[2]<='9' &&
4579        move[3]>='a' && move[3]<='x'    ) {
4580         /* input move, Shogi -> normal */
4581         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4582         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4583         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4584         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4585     } else
4586     if(move[1]=='@' &&
4587        move[3]>='0' && move[3]<='9' &&
4588        move[2]>='a' && move[2]<='x'    ) {
4589         move[1] = '*';
4590         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4591         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4592     } else
4593     if(
4594        move[0]>='a' && move[0]<='x' &&
4595        move[3]>='0' && move[3]<='9' &&
4596        move[2]>='a' && move[2]<='x'    ) {
4597          /* output move, normal -> Shogi */
4598         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4599         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4600         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4601         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4602         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4603     }
4604     if (appData.debugMode) {
4605         fprintf(debugFP, "   out = '%s'\n", move);
4606     }
4607 }
4608
4609 /* Parser for moves from gnuchess, ICS, or user typein box */
4610 Boolean
4611 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4612      char *move;
4613      int moveNum;
4614      ChessMove *moveType;
4615      int *fromX, *fromY, *toX, *toY;
4616      char *promoChar;
4617 {       
4618     if (appData.debugMode) {
4619         fprintf(debugFP, "move to parse: %s\n", move);
4620     }
4621     *moveType = yylexstr(moveNum, move);
4622
4623     switch (*moveType) {
4624       case WhitePromotionChancellor:
4625       case BlackPromotionChancellor:
4626       case WhitePromotionArchbishop:
4627       case BlackPromotionArchbishop:
4628       case WhitePromotionQueen:
4629       case BlackPromotionQueen:
4630       case WhitePromotionRook:
4631       case BlackPromotionRook:
4632       case WhitePromotionBishop:
4633       case BlackPromotionBishop:
4634       case WhitePromotionKnight:
4635       case BlackPromotionKnight:
4636       case WhitePromotionKing:
4637       case BlackPromotionKing:
4638       case NormalMove:
4639       case WhiteCapturesEnPassant:
4640       case BlackCapturesEnPassant:
4641       case WhiteKingSideCastle:
4642       case WhiteQueenSideCastle:
4643       case BlackKingSideCastle:
4644       case BlackQueenSideCastle:
4645       case WhiteKingSideCastleWild:
4646       case WhiteQueenSideCastleWild:
4647       case BlackKingSideCastleWild:
4648       case BlackQueenSideCastleWild:
4649       /* Code added by Tord: */
4650       case WhiteHSideCastleFR:
4651       case WhiteASideCastleFR:
4652       case BlackHSideCastleFR:
4653       case BlackASideCastleFR:
4654       /* End of code added by Tord */
4655       case IllegalMove:         /* bug or odd chess variant */
4656         *fromX = currentMoveString[0] - AAA;
4657         *fromY = currentMoveString[1] - ONE;
4658         *toX = currentMoveString[2] - AAA;
4659         *toY = currentMoveString[3] - ONE;
4660         *promoChar = currentMoveString[4];
4661         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4662             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4663     if (appData.debugMode) {
4664         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4665     }
4666             *fromX = *fromY = *toX = *toY = 0;
4667             return FALSE;
4668         }
4669         if (appData.testLegality) {
4670           return (*moveType != IllegalMove);
4671         } else {
4672           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4673                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4674         }
4675
4676       case WhiteDrop:
4677       case BlackDrop:
4678         *fromX = *moveType == WhiteDrop ?
4679           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4680           (int) CharToPiece(ToLower(currentMoveString[0]));
4681         *fromY = DROP_RANK;
4682         *toX = currentMoveString[2] - AAA;
4683         *toY = currentMoveString[3] - ONE;
4684         *promoChar = NULLCHAR;
4685         return TRUE;
4686
4687       case AmbiguousMove:
4688       case ImpossibleMove:
4689       case (ChessMove) 0:       /* end of file */
4690       case ElapsedTime:
4691       case Comment:
4692       case PGNTag:
4693       case NAG:
4694       case WhiteWins:
4695       case BlackWins:
4696       case GameIsDrawn:
4697       default:
4698     if (appData.debugMode) {
4699         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4700     }
4701         /* bug? */
4702         *fromX = *fromY = *toX = *toY = 0;
4703         *promoChar = NULLCHAR;
4704         return FALSE;
4705     }
4706 }
4707
4708
4709 void
4710 ParsePV(char *pv)
4711 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4712   int fromX, fromY, toX, toY; char promoChar;
4713   ChessMove moveType;
4714   Boolean valid;
4715   int nr = 0;
4716
4717   endPV = forwardMostMove;
4718   do {
4719     while(*pv == ' ') pv++;
4720     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4721     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4722 if(appData.debugMode){
4723 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4724 }
4725     if(!valid && nr == 0 &&
4726        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4727         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4728     }
4729     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4730     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4731     nr++;
4732     if(endPV+1 > framePtr) break; // no space, truncate
4733     if(!valid) break;
4734     endPV++;
4735     CopyBoard(boards[endPV], boards[endPV-1]);
4736     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4737     moveList[endPV-1][0] = fromX + AAA;
4738     moveList[endPV-1][1] = fromY + ONE;
4739     moveList[endPV-1][2] = toX + AAA;
4740     moveList[endPV-1][3] = toY + ONE;
4741     parseList[endPV-1][0] = NULLCHAR;
4742   } while(valid);
4743   currentMove = endPV;
4744   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4745   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4746                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4747   DrawPosition(TRUE, boards[currentMove]);
4748 }
4749
4750 static int lastX, lastY;
4751
4752 Boolean
4753 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4754 {
4755         int startPV;
4756
4757         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4758         lastX = x; lastY = y;
4759         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4760         startPV = index;
4761       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4762       index = startPV;
4763         while(buf[index] && buf[index] != '\n') index++;
4764         buf[index] = 0;
4765         ParsePV(buf+startPV);
4766         *start = startPV; *end = index-1;
4767         return TRUE;
4768 }
4769
4770 Boolean
4771 LoadPV(int x, int y)
4772 { // called on right mouse click to load PV
4773   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4774   lastX = x; lastY = y;
4775   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4776   return TRUE;
4777 }
4778
4779 void
4780 UnLoadPV()
4781 {
4782   if(endPV < 0) return;
4783   endPV = -1;
4784   currentMove = forwardMostMove;
4785   ClearPremoveHighlights();
4786   DrawPosition(TRUE, boards[currentMove]);
4787 }
4788
4789 void
4790 MovePV(int x, int y, int h)
4791 { // step through PV based on mouse coordinates (called on mouse move)
4792   int margin = h>>3, step = 0;
4793
4794   if(endPV < 0) return;
4795   // we must somehow check if right button is still down (might be released off board!)
4796   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4797   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4798   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4799   if(!step) return;
4800   lastX = x; lastY = y;
4801   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4802   currentMove += step;
4803   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4804   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4805                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4806   DrawPosition(FALSE, boards[currentMove]);
4807 }
4808
4809
4810 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4811 // All positions will have equal probability, but the current method will not provide a unique
4812 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4813 #define DARK 1
4814 #define LITE 2
4815 #define ANY 3
4816
4817 int squaresLeft[4];
4818 int piecesLeft[(int)BlackPawn];
4819 int seed, nrOfShuffles;
4820
4821 void GetPositionNumber()
4822 {       // sets global variable seed
4823         int i;
4824
4825         seed = appData.defaultFrcPosition;
4826         if(seed < 0) { // randomize based on time for negative FRC position numbers
4827                 for(i=0; i<50; i++) seed += random();
4828                 seed = random() ^ random() >> 8 ^ random() << 8;
4829                 if(seed<0) seed = -seed;
4830         }
4831 }
4832
4833 int put(Board board, int pieceType, int rank, int n, int shade)
4834 // put the piece on the (n-1)-th empty squares of the given shade
4835 {
4836         int i;
4837
4838         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4839                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4840                         board[rank][i] = (ChessSquare) pieceType;
4841                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4842                         squaresLeft[ANY]--;
4843                         piecesLeft[pieceType]--; 
4844                         return i;
4845                 }
4846         }
4847         return -1;
4848 }
4849
4850
4851 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4852 // calculate where the next piece goes, (any empty square), and put it there
4853 {
4854         int i;
4855
4856         i = seed % squaresLeft[shade];
4857         nrOfShuffles *= squaresLeft[shade];
4858         seed /= squaresLeft[shade];
4859         put(board, pieceType, rank, i, shade);
4860 }
4861
4862 void AddTwoPieces(Board board, int pieceType, int rank)
4863 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4864 {
4865         int i, n=squaresLeft[ANY], j=n-1, k;
4866
4867         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4868         i = seed % k;  // pick one
4869         nrOfShuffles *= k;
4870         seed /= k;
4871         while(i >= j) i -= j--;
4872         j = n - 1 - j; i += j;
4873         put(board, pieceType, rank, j, ANY);
4874         put(board, pieceType, rank, i, ANY);
4875 }
4876
4877 void SetUpShuffle(Board board, int number)
4878 {
4879         int i, p, first=1;
4880
4881         GetPositionNumber(); nrOfShuffles = 1;
4882
4883         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4884         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4885         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4886
4887         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4888
4889         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4890             p = (int) board[0][i];
4891             if(p < (int) BlackPawn) piecesLeft[p] ++;
4892             board[0][i] = EmptySquare;
4893         }
4894
4895         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4896             // shuffles restricted to allow normal castling put KRR first
4897             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4898                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4899             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4900                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4901             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4902                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4903             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4904                 put(board, WhiteRook, 0, 0, ANY);
4905             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4906         }
4907
4908         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4909             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4910             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4911                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4912                 while(piecesLeft[p] >= 2) {
4913                     AddOnePiece(board, p, 0, LITE);
4914                     AddOnePiece(board, p, 0, DARK);
4915                 }
4916                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4917             }
4918
4919         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4920             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4921             // but we leave King and Rooks for last, to possibly obey FRC restriction
4922             if(p == (int)WhiteRook) continue;
4923             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4924             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4925         }
4926
4927         // now everything is placed, except perhaps King (Unicorn) and Rooks
4928
4929         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4930             // Last King gets castling rights
4931             while(piecesLeft[(int)WhiteUnicorn]) {
4932                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4933                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4934             }
4935
4936             while(piecesLeft[(int)WhiteKing]) {
4937                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4938                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4939             }
4940
4941
4942         } else {
4943             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4944             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4945         }
4946
4947         // Only Rooks can be left; simply place them all
4948         while(piecesLeft[(int)WhiteRook]) {
4949                 i = put(board, WhiteRook, 0, 0, ANY);
4950                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4951                         if(first) {
4952                                 first=0;
4953                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4954                         }
4955                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4956                 }
4957         }
4958         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4959             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4960         }
4961
4962         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4963 }
4964
4965 int SetCharTable( char *table, const char * map )
4966 /* [HGM] moved here from winboard.c because of its general usefulness */
4967 /*       Basically a safe strcpy that uses the last character as King */
4968 {
4969     int result = FALSE; int NrPieces;
4970
4971     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4972                     && NrPieces >= 12 && !(NrPieces&1)) {
4973         int i; /* [HGM] Accept even length from 12 to 34 */
4974
4975         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4976         for( i=0; i<NrPieces/2-1; i++ ) {
4977             table[i] = map[i];
4978             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4979         }
4980         table[(int) WhiteKing]  = map[NrPieces/2-1];
4981         table[(int) BlackKing]  = map[NrPieces-1];
4982
4983         result = TRUE;
4984     }
4985
4986     return result;
4987 }
4988
4989 void Prelude(Board board)
4990 {       // [HGM] superchess: random selection of exo-pieces
4991         int i, j, k; ChessSquare p; 
4992         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4993
4994         GetPositionNumber(); // use FRC position number
4995
4996         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4997             SetCharTable(pieceToChar, appData.pieceToCharTable);
4998             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
4999                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5000         }
5001
5002         j = seed%4;                 seed /= 4; 
5003         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5004         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5005         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5006         j = seed%3 + (seed%3 >= j); seed /= 3; 
5007         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5008         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5009         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5010         j = seed%3;                 seed /= 3; 
5011         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5012         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5013         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5014         j = seed%2 + (seed%2 >= j); seed /= 2; 
5015         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5016         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5017         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5018         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5019         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5020         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5021         put(board, exoPieces[0],    0, 0, ANY);
5022         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5023 }
5024
5025 void
5026 InitPosition(redraw)
5027      int redraw;
5028 {
5029     ChessSquare (* pieces)[BOARD_FILES];
5030     int i, j, pawnRow, overrule,
5031     oldx = gameInfo.boardWidth,
5032     oldy = gameInfo.boardHeight,
5033     oldh = gameInfo.holdingsWidth,
5034     oldv = gameInfo.variant;
5035
5036     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5037
5038     /* [AS] Initialize pv info list [HGM] and game status */
5039     {
5040         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5041             pvInfoList[i].depth = 0;
5042             boards[i][EP_STATUS] = EP_NONE;
5043             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5044         }
5045
5046         initialRulePlies = 0; /* 50-move counter start */
5047
5048         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5049         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5050     }
5051
5052     
5053     /* [HGM] logic here is completely changed. In stead of full positions */
5054     /* the initialized data only consist of the two backranks. The switch */
5055     /* selects which one we will use, which is than copied to the Board   */
5056     /* initialPosition, which for the rest is initialized by Pawns and    */
5057     /* empty squares. This initial position is then copied to boards[0],  */
5058     /* possibly after shuffling, so that it remains available.            */
5059
5060     gameInfo.holdingsWidth = 0; /* default board sizes */
5061     gameInfo.boardWidth    = 8;
5062     gameInfo.boardHeight   = 8;
5063     gameInfo.holdingsSize  = 0;
5064     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5065     for(i=0; i<BOARD_FILES-2; i++)
5066       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5067     initialPosition[EP_STATUS] = EP_NONE;
5068     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5069
5070     switch (gameInfo.variant) {
5071     case VariantFischeRandom:
5072       shuffleOpenings = TRUE;
5073     default:
5074       pieces = FIDEArray;
5075       break;
5076     case VariantShatranj:
5077       pieces = ShatranjArray;
5078       nrCastlingRights = 0;
5079       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5080       break;
5081     case VariantMakruk:
5082       pieces = makrukArray;
5083       nrCastlingRights = 0;
5084       startedFromSetupPosition = TRUE;
5085       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5086       break;
5087     case VariantTwoKings:
5088       pieces = twoKingsArray;
5089       break;
5090     case VariantCapaRandom:
5091       shuffleOpenings = TRUE;
5092     case VariantCapablanca:
5093       pieces = CapablancaArray;
5094       gameInfo.boardWidth = 10;
5095       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5096       break;
5097     case VariantGothic:
5098       pieces = GothicArray;
5099       gameInfo.boardWidth = 10;
5100       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5101       break;
5102     case VariantJanus:
5103       pieces = JanusArray;
5104       gameInfo.boardWidth = 10;
5105       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5106       nrCastlingRights = 6;
5107         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5108         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5109         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5110         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5111         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5112         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5113       break;
5114     case VariantFalcon:
5115       pieces = FalconArray;
5116       gameInfo.boardWidth = 10;
5117       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5118       break;
5119     case VariantXiangqi:
5120       pieces = XiangqiArray;
5121       gameInfo.boardWidth  = 9;
5122       gameInfo.boardHeight = 10;
5123       nrCastlingRights = 0;
5124       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5125       break;
5126     case VariantShogi:
5127       pieces = ShogiArray;
5128       gameInfo.boardWidth  = 9;
5129       gameInfo.boardHeight = 9;
5130       gameInfo.holdingsSize = 7;
5131       nrCastlingRights = 0;
5132       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5133       break;
5134     case VariantCourier:
5135       pieces = CourierArray;
5136       gameInfo.boardWidth  = 12;
5137       nrCastlingRights = 0;
5138       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5139       break;
5140     case VariantKnightmate:
5141       pieces = KnightmateArray;
5142       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5143       break;
5144     case VariantFairy:
5145       pieces = fairyArray;
5146       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5147       break;
5148     case VariantGreat:
5149       pieces = GreatArray;
5150       gameInfo.boardWidth = 10;
5151       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5152       gameInfo.holdingsSize = 8;
5153       break;
5154     case VariantSuper:
5155       pieces = FIDEArray;
5156       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5157       gameInfo.holdingsSize = 8;
5158       startedFromSetupPosition = TRUE;
5159       break;
5160     case VariantCrazyhouse:
5161     case VariantBughouse:
5162       pieces = FIDEArray;
5163       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5164       gameInfo.holdingsSize = 5;
5165       break;
5166     case VariantWildCastle:
5167       pieces = FIDEArray;
5168       /* !!?shuffle with kings guaranteed to be on d or e file */
5169       shuffleOpenings = 1;
5170       break;
5171     case VariantNoCastle:
5172       pieces = FIDEArray;
5173       nrCastlingRights = 0;
5174       /* !!?unconstrained back-rank shuffle */
5175       shuffleOpenings = 1;
5176       break;
5177     }
5178
5179     overrule = 0;
5180     if(appData.NrFiles >= 0) {
5181         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5182         gameInfo.boardWidth = appData.NrFiles;
5183     }
5184     if(appData.NrRanks >= 0) {
5185         gameInfo.boardHeight = appData.NrRanks;
5186     }
5187     if(appData.holdingsSize >= 0) {
5188         i = appData.holdingsSize;
5189         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5190         gameInfo.holdingsSize = i;
5191     }
5192     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5193     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5194         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5195
5196     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5197     if(pawnRow < 1) pawnRow = 1;
5198     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5199
5200     /* User pieceToChar list overrules defaults */
5201     if(appData.pieceToCharTable != NULL)
5202         SetCharTable(pieceToChar, appData.pieceToCharTable);
5203
5204     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5205
5206         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5207             s = (ChessSquare) 0; /* account holding counts in guard band */
5208         for( i=0; i<BOARD_HEIGHT; i++ )
5209             initialPosition[i][j] = s;
5210
5211         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5212         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5213         initialPosition[pawnRow][j] = WhitePawn;
5214         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5215         if(gameInfo.variant == VariantXiangqi) {
5216             if(j&1) {
5217                 initialPosition[pawnRow][j] = 
5218                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5219                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5220                    initialPosition[2][j] = WhiteCannon;
5221                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5222                 }
5223             }
5224         }
5225         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5226     }
5227     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5228
5229             j=BOARD_LEFT+1;
5230             initialPosition[1][j] = WhiteBishop;
5231             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5232             j=BOARD_RGHT-2;
5233             initialPosition[1][j] = WhiteRook;
5234             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5235     }
5236
5237     if( nrCastlingRights == -1) {
5238         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5239         /*       This sets default castling rights from none to normal corners   */
5240         /* Variants with other castling rights must set them themselves above    */
5241         nrCastlingRights = 6;
5242        
5243         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5244         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5245         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5246         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5247         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5248         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5249      }
5250
5251      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5252      if(gameInfo.variant == VariantGreat) { // promotion commoners
5253         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5254         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5255         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5256         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5257      }
5258   if (appData.debugMode) {
5259     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5260   }
5261     if(shuffleOpenings) {
5262         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5263         startedFromSetupPosition = TRUE;
5264     }
5265     if(startedFromPositionFile) {
5266       /* [HGM] loadPos: use PositionFile for every new game */
5267       CopyBoard(initialPosition, filePosition);
5268       for(i=0; i<nrCastlingRights; i++)
5269           initialRights[i] = filePosition[CASTLING][i];
5270       startedFromSetupPosition = TRUE;
5271     }
5272
5273     CopyBoard(boards[0], initialPosition);
5274
5275     if(oldx != gameInfo.boardWidth ||
5276        oldy != gameInfo.boardHeight ||
5277        oldh != gameInfo.holdingsWidth
5278 #ifdef GOTHIC
5279        || oldv == VariantGothic ||        // For licensing popups
5280        gameInfo.variant == VariantGothic
5281 #endif
5282 #ifdef FALCON
5283        || oldv == VariantFalcon ||
5284        gameInfo.variant == VariantFalcon
5285 #endif
5286                                          )
5287             InitDrawingSizes(-2 ,0);
5288
5289     if (redraw)
5290       DrawPosition(TRUE, boards[currentMove]);
5291 }
5292
5293 void
5294 SendBoard(cps, moveNum)
5295      ChessProgramState *cps;
5296      int moveNum;
5297 {
5298     char message[MSG_SIZ];
5299     
5300     if (cps->useSetboard) {
5301       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5302       sprintf(message, "setboard %s\n", fen);
5303       SendToProgram(message, cps);
5304       free(fen);
5305
5306     } else {
5307       ChessSquare *bp;
5308       int i, j;
5309       /* Kludge to set black to move, avoiding the troublesome and now
5310        * deprecated "black" command.
5311        */
5312       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5313
5314       SendToProgram("edit\n", cps);
5315       SendToProgram("#\n", cps);
5316       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5317         bp = &boards[moveNum][i][BOARD_LEFT];
5318         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5319           if ((int) *bp < (int) BlackPawn) {
5320             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5321                     AAA + j, ONE + i);
5322             if(message[0] == '+' || message[0] == '~') {
5323                 sprintf(message, "%c%c%c+\n",
5324                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5325                         AAA + j, ONE + i);
5326             }
5327             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5328                 message[1] = BOARD_RGHT   - 1 - j + '1';
5329                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5330             }
5331             SendToProgram(message, cps);
5332           }
5333         }
5334       }
5335     
5336       SendToProgram("c\n", cps);
5337       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5338         bp = &boards[moveNum][i][BOARD_LEFT];
5339         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5340           if (((int) *bp != (int) EmptySquare)
5341               && ((int) *bp >= (int) BlackPawn)) {
5342             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5343                     AAA + j, ONE + i);
5344             if(message[0] == '+' || message[0] == '~') {
5345                 sprintf(message, "%c%c%c+\n",
5346                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5347                         AAA + j, ONE + i);
5348             }
5349             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5350                 message[1] = BOARD_RGHT   - 1 - j + '1';
5351                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5352             }
5353             SendToProgram(message, cps);
5354           }
5355         }
5356       }
5357     
5358       SendToProgram(".\n", cps);
5359     }
5360     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5361 }
5362
5363 static int autoQueen; // [HGM] oneclick
5364
5365 int
5366 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5367 {
5368     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5369     /* [HGM] add Shogi promotions */
5370     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5371     ChessSquare piece;
5372     ChessMove moveType;
5373     Boolean premove;
5374
5375     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5376     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5377
5378     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5379       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5380         return FALSE;
5381
5382     piece = boards[currentMove][fromY][fromX];
5383     if(gameInfo.variant == VariantShogi) {
5384         promotionZoneSize = 3;
5385         highestPromotingPiece = (int)WhiteFerz;
5386     } else if(gameInfo.variant == VariantMakruk) {
5387         promotionZoneSize = 3;
5388     }
5389
5390     // next weed out all moves that do not touch the promotion zone at all
5391     if((int)piece >= BlackPawn) {
5392         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5393              return FALSE;
5394         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5395     } else {
5396         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5397            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5398     }
5399
5400     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5401
5402     // weed out mandatory Shogi promotions
5403     if(gameInfo.variant == VariantShogi) {
5404         if(piece >= BlackPawn) {
5405             if(toY == 0 && piece == BlackPawn ||
5406                toY == 0 && piece == BlackQueen ||
5407                toY <= 1 && piece == BlackKnight) {
5408                 *promoChoice = '+';
5409                 return FALSE;
5410             }
5411         } else {
5412             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5413                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5414                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5415                 *promoChoice = '+';
5416                 return FALSE;
5417             }
5418         }
5419     }
5420
5421     // weed out obviously illegal Pawn moves
5422     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5423         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5424         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5425         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5426         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5427         // note we are not allowed to test for valid (non-)capture, due to premove
5428     }
5429
5430     // we either have a choice what to promote to, or (in Shogi) whether to promote
5431     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5432         *promoChoice = PieceToChar(BlackFerz);  // no choice
5433         return FALSE;
5434     }
5435     if(autoQueen) { // predetermined
5436         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5437              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5438         else *promoChoice = PieceToChar(BlackQueen);
5439         return FALSE;
5440     }
5441
5442     // suppress promotion popup on illegal moves that are not premoves
5443     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5444               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5445     if(appData.testLegality && !premove) {
5446         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5447                         fromY, fromX, toY, toX, NULLCHAR);
5448         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5449            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5450             return FALSE;
5451     }
5452
5453     return TRUE;
5454 }
5455
5456 int
5457 InPalace(row, column)
5458      int row, column;
5459 {   /* [HGM] for Xiangqi */
5460     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5461          column < (BOARD_WIDTH + 4)/2 &&
5462          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5463     return FALSE;
5464 }
5465
5466 int
5467 PieceForSquare (x, y)
5468      int x;
5469      int y;
5470 {
5471   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5472      return -1;
5473   else
5474      return boards[currentMove][y][x];
5475 }
5476
5477 int
5478 OKToStartUserMove(x, y)
5479      int x, y;
5480 {
5481     ChessSquare from_piece;
5482     int white_piece;
5483
5484     if (matchMode) return FALSE;
5485     if (gameMode == EditPosition) return TRUE;
5486
5487     if (x >= 0 && y >= 0)
5488       from_piece = boards[currentMove][y][x];
5489     else
5490       from_piece = EmptySquare;
5491
5492     if (from_piece == EmptySquare) return FALSE;
5493
5494     white_piece = (int)from_piece >= (int)WhitePawn &&
5495       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5496
5497     switch (gameMode) {
5498       case PlayFromGameFile:
5499       case AnalyzeFile:
5500       case TwoMachinesPlay:
5501       case EndOfGame:
5502         return FALSE;
5503
5504       case IcsObserving:
5505       case IcsIdle:
5506         return FALSE;
5507
5508       case MachinePlaysWhite:
5509       case IcsPlayingBlack:
5510         if (appData.zippyPlay) return FALSE;
5511         if (white_piece) {
5512             DisplayMoveError(_("You are playing Black"));
5513             return FALSE;
5514         }
5515         break;
5516
5517       case MachinePlaysBlack:
5518       case IcsPlayingWhite:
5519         if (appData.zippyPlay) return FALSE;
5520         if (!white_piece) {
5521             DisplayMoveError(_("You are playing White"));
5522             return FALSE;
5523         }
5524         break;
5525
5526       case EditGame:
5527         if (!white_piece && WhiteOnMove(currentMove)) {
5528             DisplayMoveError(_("It is White's turn"));
5529             return FALSE;
5530         }           
5531         if (white_piece && !WhiteOnMove(currentMove)) {
5532             DisplayMoveError(_("It is Black's turn"));
5533             return FALSE;
5534         }           
5535         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5536             /* Editing correspondence game history */
5537             /* Could disallow this or prompt for confirmation */
5538             cmailOldMove = -1;
5539         }
5540         break;
5541
5542       case BeginningOfGame:
5543         if (appData.icsActive) return FALSE;
5544         if (!appData.noChessProgram) {
5545             if (!white_piece) {
5546                 DisplayMoveError(_("You are playing White"));
5547                 return FALSE;
5548             }
5549         }
5550         break;
5551         
5552       case Training:
5553         if (!white_piece && WhiteOnMove(currentMove)) {
5554             DisplayMoveError(_("It is White's turn"));
5555             return FALSE;
5556         }           
5557         if (white_piece && !WhiteOnMove(currentMove)) {
5558             DisplayMoveError(_("It is Black's turn"));
5559             return FALSE;
5560         }           
5561         break;
5562
5563       default:
5564       case IcsExamining:
5565         break;
5566     }
5567     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5568         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5569         && gameMode != AnalyzeFile && gameMode != Training) {
5570         DisplayMoveError(_("Displayed position is not current"));
5571         return FALSE;
5572     }
5573     return TRUE;
5574 }
5575
5576 Boolean
5577 OnlyMove(int *x, int *y, Boolean captures) {
5578     DisambiguateClosure cl;
5579     if (appData.zippyPlay) return FALSE;
5580     switch(gameMode) {
5581       case MachinePlaysBlack:
5582       case IcsPlayingWhite:
5583       case BeginningOfGame:
5584         if(!WhiteOnMove(currentMove)) return FALSE;
5585         break;
5586       case MachinePlaysWhite:
5587       case IcsPlayingBlack:
5588         if(WhiteOnMove(currentMove)) return FALSE;
5589         break;
5590       default:
5591         return FALSE;
5592     }
5593     cl.pieceIn = EmptySquare; 
5594     cl.rfIn = *y;
5595     cl.ffIn = *x;
5596     cl.rtIn = -1;
5597     cl.ftIn = -1;
5598     cl.promoCharIn = NULLCHAR;
5599     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5600     if( cl.kind == NormalMove ||
5601         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5602         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5603         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5604         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5605       fromX = cl.ff;
5606       fromY = cl.rf;
5607       *x = cl.ft;
5608       *y = cl.rt;
5609       return TRUE;
5610     }
5611     if(cl.kind != ImpossibleMove) return FALSE;
5612     cl.pieceIn = EmptySquare;
5613     cl.rfIn = -1;
5614     cl.ffIn = -1;
5615     cl.rtIn = *y;
5616     cl.ftIn = *x;
5617     cl.promoCharIn = NULLCHAR;
5618     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5619     if( cl.kind == NormalMove ||
5620         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5621         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5622         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5623         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5624       fromX = cl.ff;
5625       fromY = cl.rf;
5626       *x = cl.ft;
5627       *y = cl.rt;
5628       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5629       return TRUE;
5630     }
5631     return FALSE;
5632 }
5633
5634 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5635 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5636 int lastLoadGameUseList = FALSE;
5637 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5638 ChessMove lastLoadGameStart = (ChessMove) 0;
5639
5640 ChessMove
5641 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5642      int fromX, fromY, toX, toY;
5643      int promoChar;
5644      Boolean captureOwn;
5645 {
5646     ChessMove moveType;
5647     ChessSquare pdown, pup;
5648
5649     /* Check if the user is playing in turn.  This is complicated because we
5650        let the user "pick up" a piece before it is his turn.  So the piece he
5651        tried to pick up may have been captured by the time he puts it down!
5652        Therefore we use the color the user is supposed to be playing in this
5653        test, not the color of the piece that is currently on the starting
5654        square---except in EditGame mode, where the user is playing both
5655        sides; fortunately there the capture race can't happen.  (It can
5656        now happen in IcsExamining mode, but that's just too bad.  The user
5657        will get a somewhat confusing message in that case.)
5658        */
5659
5660     switch (gameMode) {
5661       case PlayFromGameFile:
5662       case AnalyzeFile:
5663       case TwoMachinesPlay:
5664       case EndOfGame:
5665       case IcsObserving:
5666       case IcsIdle:
5667         /* We switched into a game mode where moves are not accepted,
5668            perhaps while the mouse button was down. */
5669         return ImpossibleMove;
5670
5671       case MachinePlaysWhite:
5672         /* User is moving for Black */
5673         if (WhiteOnMove(currentMove)) {
5674             DisplayMoveError(_("It is White's turn"));
5675             return ImpossibleMove;
5676         }
5677         break;
5678
5679       case MachinePlaysBlack:
5680         /* User is moving for White */
5681         if (!WhiteOnMove(currentMove)) {
5682             DisplayMoveError(_("It is Black's turn"));
5683             return ImpossibleMove;
5684         }
5685         break;
5686
5687       case EditGame:
5688       case IcsExamining:
5689       case BeginningOfGame:
5690       case AnalyzeMode:
5691       case Training:
5692         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5693             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5694             /* User is moving for Black */
5695             if (WhiteOnMove(currentMove)) {
5696                 DisplayMoveError(_("It is White's turn"));
5697                 return ImpossibleMove;
5698             }
5699         } else {
5700             /* User is moving for White */
5701             if (!WhiteOnMove(currentMove)) {
5702                 DisplayMoveError(_("It is Black's turn"));
5703                 return ImpossibleMove;
5704             }
5705         }
5706         break;
5707
5708       case IcsPlayingBlack:
5709         /* User is moving for Black */
5710         if (WhiteOnMove(currentMove)) {
5711             if (!appData.premove) {
5712                 DisplayMoveError(_("It is White's turn"));
5713             } else if (toX >= 0 && toY >= 0) {
5714                 premoveToX = toX;
5715                 premoveToY = toY;
5716                 premoveFromX = fromX;
5717                 premoveFromY = fromY;
5718                 premovePromoChar = promoChar;
5719                 gotPremove = 1;
5720                 if (appData.debugMode) 
5721                     fprintf(debugFP, "Got premove: fromX %d,"
5722                             "fromY %d, toX %d, toY %d\n",
5723                             fromX, fromY, toX, toY);
5724             }
5725             return ImpossibleMove;
5726         }
5727         break;
5728
5729       case IcsPlayingWhite:
5730         /* User is moving for White */
5731         if (!WhiteOnMove(currentMove)) {
5732             if (!appData.premove) {
5733                 DisplayMoveError(_("It is Black's turn"));
5734             } else if (toX >= 0 && toY >= 0) {
5735                 premoveToX = toX;
5736                 premoveToY = toY;
5737                 premoveFromX = fromX;
5738                 premoveFromY = fromY;
5739                 premovePromoChar = promoChar;
5740                 gotPremove = 1;
5741                 if (appData.debugMode) 
5742                     fprintf(debugFP, "Got premove: fromX %d,"
5743                             "fromY %d, toX %d, toY %d\n",
5744                             fromX, fromY, toX, toY);
5745             }
5746             return ImpossibleMove;
5747         }
5748         break;
5749
5750       default:
5751         break;
5752
5753       case EditPosition:
5754         /* EditPosition, empty square, or different color piece;
5755            click-click move is possible */
5756         if (toX == -2 || toY == -2) {
5757             boards[0][fromY][fromX] = EmptySquare;
5758             return AmbiguousMove;
5759         } else if (toX >= 0 && toY >= 0) {
5760             boards[0][toY][toX] = boards[0][fromY][fromX];
5761             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5762                 if(boards[0][fromY][0] != EmptySquare) {
5763                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5764                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5765                 }
5766             } else
5767             if(fromX == BOARD_RGHT+1) {
5768                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5769                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5770                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5771                 }
5772             } else
5773             boards[0][fromY][fromX] = EmptySquare;
5774             return AmbiguousMove;
5775         }
5776         return ImpossibleMove;
5777     }
5778
5779     if(toX < 0 || toY < 0) return ImpossibleMove;
5780     pdown = boards[currentMove][fromY][fromX];
5781     pup = boards[currentMove][toY][toX];
5782
5783     /* [HGM] If move started in holdings, it means a drop */
5784     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5785          if( pup != EmptySquare ) return ImpossibleMove;
5786          if(appData.testLegality) {
5787              /* it would be more logical if LegalityTest() also figured out
5788               * which drops are legal. For now we forbid pawns on back rank.
5789               * Shogi is on its own here...
5790               */
5791              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5792                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5793                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5794          }
5795          return WhiteDrop; /* Not needed to specify white or black yet */
5796     }
5797
5798     /* [HGM] always test for legality, to get promotion info */
5799     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5800                                          fromY, fromX, toY, toX, promoChar);
5801     /* [HGM] but possibly ignore an IllegalMove result */
5802     if (appData.testLegality) {
5803         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5804             DisplayMoveError(_("Illegal move"));
5805             return ImpossibleMove;
5806         }
5807     }
5808
5809     return moveType;
5810     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5811        function is made into one that returns an OK move type if FinishMove
5812        should be called. This to give the calling driver routine the
5813        opportunity to finish the userMove input with a promotion popup,
5814        without bothering the user with this for invalid or illegal moves */
5815
5816 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5817 }
5818
5819 /* Common tail of UserMoveEvent and DropMenuEvent */
5820 int
5821 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5822      ChessMove moveType;
5823      int fromX, fromY, toX, toY;
5824      /*char*/int promoChar;
5825 {
5826     char *bookHit = 0;
5827
5828     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5829         // [HGM] superchess: suppress promotions to non-available piece
5830         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5831         if(WhiteOnMove(currentMove)) {
5832             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5833         } else {
5834             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5835         }
5836     }
5837
5838     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5839        move type in caller when we know the move is a legal promotion */
5840     if(moveType == NormalMove && promoChar)
5841         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5842
5843     /* [HGM] convert drag-and-drop piece drops to standard form */
5844     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5845          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5846            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5847                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5848            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5849            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5850            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5851            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5852          fromY = DROP_RANK;
5853     }
5854
5855     /* [HGM] <popupFix> The following if has been moved here from
5856        UserMoveEvent(). Because it seemed to belong here (why not allow
5857        piece drops in training games?), and because it can only be
5858        performed after it is known to what we promote. */
5859     if (gameMode == Training) {
5860       /* compare the move played on the board to the next move in the
5861        * game. If they match, display the move and the opponent's response. 
5862        * If they don't match, display an error message.
5863        */
5864       int saveAnimate;
5865       Board testBoard;
5866       CopyBoard(testBoard, boards[currentMove]);
5867       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5868
5869       if (CompareBoards(testBoard, boards[currentMove+1])) {
5870         ForwardInner(currentMove+1);
5871
5872         /* Autoplay the opponent's response.
5873          * if appData.animate was TRUE when Training mode was entered,
5874          * the response will be animated.
5875          */
5876         saveAnimate = appData.animate;
5877         appData.animate = animateTraining;
5878         ForwardInner(currentMove+1);
5879         appData.animate = saveAnimate;
5880
5881         /* check for the end of the game */
5882         if (currentMove >= forwardMostMove) {
5883           gameMode = PlayFromGameFile;
5884           ModeHighlight();
5885           SetTrainingModeOff();
5886           DisplayInformation(_("End of game"));
5887         }
5888       } else {
5889         DisplayError(_("Incorrect move"), 0);
5890       }
5891       return 1;
5892     }
5893
5894   /* Ok, now we know that the move is good, so we can kill
5895      the previous line in Analysis Mode */
5896   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5897                                 && currentMove < forwardMostMove) {
5898     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5899   }
5900
5901   /* If we need the chess program but it's dead, restart it */
5902   ResurrectChessProgram();
5903
5904   /* A user move restarts a paused game*/
5905   if (pausing)
5906     PauseEvent();
5907
5908   thinkOutput[0] = NULLCHAR;
5909
5910   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5911
5912   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5913
5914   if (gameMode == BeginningOfGame) {
5915     if (appData.noChessProgram) {
5916       gameMode = EditGame;
5917       SetGameInfo();
5918     } else {
5919       char buf[MSG_SIZ];
5920       gameMode = MachinePlaysBlack;
5921       StartClocks();
5922       SetGameInfo();
5923       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5924       DisplayTitle(buf);
5925       if (first.sendName) {
5926         sprintf(buf, "name %s\n", gameInfo.white);
5927         SendToProgram(buf, &first);
5928       }
5929       StartClocks();
5930     }
5931     ModeHighlight();
5932   }
5933
5934   /* Relay move to ICS or chess engine */
5935   if (appData.icsActive) {
5936     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5937         gameMode == IcsExamining) {
5938       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5939         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5940         SendToICS("draw ");
5941         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5942       }
5943       // also send plain move, in case ICS does not understand atomic claims
5944       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5945       ics_user_moved = 1;
5946     }
5947   } else {
5948     if (first.sendTime && (gameMode == BeginningOfGame ||
5949                            gameMode == MachinePlaysWhite ||
5950                            gameMode == MachinePlaysBlack)) {
5951       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5952     }
5953     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5954          // [HGM] book: if program might be playing, let it use book
5955         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5956         first.maybeThinking = TRUE;
5957     } else SendMoveToProgram(forwardMostMove-1, &first);
5958     if (currentMove == cmailOldMove + 1) {
5959       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5960     }
5961   }
5962
5963   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5964
5965   switch (gameMode) {
5966   case EditGame:
5967     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5968     case MT_NONE:
5969     case MT_CHECK:
5970       break;
5971     case MT_CHECKMATE:
5972     case MT_STAINMATE:
5973       if (WhiteOnMove(currentMove)) {
5974         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5975       } else {
5976         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5977       }
5978       break;
5979     case MT_STALEMATE:
5980       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5981       break;
5982     }
5983     break;
5984     
5985   case MachinePlaysBlack:
5986   case MachinePlaysWhite:
5987     /* disable certain menu options while machine is thinking */
5988     SetMachineThinkingEnables();
5989     break;
5990
5991   default:
5992     break;
5993   }
5994
5995   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5996         
5997   if(bookHit) { // [HGM] book: simulate book reply
5998         static char bookMove[MSG_SIZ]; // a bit generous?
5999
6000         programStats.nodes = programStats.depth = programStats.time = 
6001         programStats.score = programStats.got_only_move = 0;
6002         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6003
6004         strcpy(bookMove, "move ");
6005         strcat(bookMove, bookHit);
6006         HandleMachineMove(bookMove, &first);
6007   }
6008   return 1;
6009 }
6010
6011 void
6012 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6013      int fromX, fromY, toX, toY;
6014      int promoChar;
6015 {
6016     /* [HGM] This routine was added to allow calling of its two logical
6017        parts from other modules in the old way. Before, UserMoveEvent()
6018        automatically called FinishMove() if the move was OK, and returned
6019        otherwise. I separated the two, in order to make it possible to
6020        slip a promotion popup in between. But that it always needs two
6021        calls, to the first part, (now called UserMoveTest() ), and to
6022        FinishMove if the first part succeeded. Calls that do not need
6023        to do anything in between, can call this routine the old way. 
6024     */
6025     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6026 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6027     if(moveType == AmbiguousMove)
6028         DrawPosition(FALSE, boards[currentMove]);
6029     else if(moveType != ImpossibleMove && moveType != Comment)
6030         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6031 }
6032
6033 void
6034 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6035      Board board;
6036      int flags;
6037      ChessMove kind;
6038      int rf, ff, rt, ft;
6039      VOIDSTAR closure;
6040 {
6041     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6042     Markers *m = (Markers *) closure;
6043     if(rf == fromY && ff == fromX)
6044         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6045                          || kind == WhiteCapturesEnPassant
6046                          || kind == BlackCapturesEnPassant);
6047     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6048 }
6049
6050 void
6051 MarkTargetSquares(int clear)
6052 {
6053   int x, y;
6054   if(!appData.markers || !appData.highlightDragging || 
6055      !appData.testLegality || gameMode == EditPosition) return;
6056   if(clear) {
6057     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6058   } else {
6059     int capt = 0;
6060     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6061     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6062       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6063       if(capt)
6064       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6065     }
6066   }
6067   DrawPosition(TRUE, NULL);
6068 }
6069
6070 void LeftClick(ClickType clickType, int xPix, int yPix)
6071 {
6072     int x, y;
6073     Boolean saveAnimate;
6074     static int second = 0, promotionChoice = 0;
6075     char promoChoice = NULLCHAR;
6076
6077     if(appData.seekGraph && appData.icsActive && loggedOn &&
6078         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6079         SeekGraphClick(clickType, xPix, yPix, 0);
6080         return;
6081     }
6082
6083     if (clickType == Press) ErrorPopDown();
6084     MarkTargetSquares(1);
6085
6086     x = EventToSquare(xPix, BOARD_WIDTH);
6087     y = EventToSquare(yPix, BOARD_HEIGHT);
6088     if (!flipView && y >= 0) {
6089         y = BOARD_HEIGHT - 1 - y;
6090     }
6091     if (flipView && x >= 0) {
6092         x = BOARD_WIDTH - 1 - x;
6093     }
6094
6095     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6096         if(clickType == Release) return; // ignore upclick of click-click destination
6097         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6098         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6099         if(gameInfo.holdingsWidth && 
6100                 (WhiteOnMove(currentMove) 
6101                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6102                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6103             // click in right holdings, for determining promotion piece
6104             ChessSquare p = boards[currentMove][y][x];
6105             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6106             if(p != EmptySquare) {
6107                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6108                 fromX = fromY = -1;
6109                 return;
6110             }
6111         }
6112         DrawPosition(FALSE, boards[currentMove]);
6113         return;
6114     }
6115
6116     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6117     if(clickType == Press
6118             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6119               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6120               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6121         return;
6122
6123     autoQueen = appData.alwaysPromoteToQueen;
6124
6125     if (fromX == -1) {
6126       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6127         if (clickType == Press) {
6128             /* First square */
6129             if (OKToStartUserMove(x, y)) {
6130                 fromX = x;
6131                 fromY = y;
6132                 second = 0;
6133                 MarkTargetSquares(0);
6134                 DragPieceBegin(xPix, yPix);
6135                 if (appData.highlightDragging) {
6136                     SetHighlights(x, y, -1, -1);
6137                 }
6138             }
6139         }
6140         return;
6141       }
6142     }
6143
6144     /* fromX != -1 */
6145     if (clickType == Press && gameMode != EditPosition) {
6146         ChessSquare fromP;
6147         ChessSquare toP;
6148         int frc;
6149
6150         // ignore off-board to clicks
6151         if(y < 0 || x < 0) return;
6152
6153         /* Check if clicking again on the same color piece */
6154         fromP = boards[currentMove][fromY][fromX];
6155         toP = boards[currentMove][y][x];
6156         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6157         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6158              WhitePawn <= toP && toP <= WhiteKing &&
6159              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6160              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6161             (BlackPawn <= fromP && fromP <= BlackKing && 
6162              BlackPawn <= toP && toP <= BlackKing &&
6163              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6164              !(fromP == BlackKing && toP == BlackRook && frc))) {
6165             /* Clicked again on same color piece -- changed his mind */
6166             second = (x == fromX && y == fromY);
6167            if(!second || !OnlyMove(&x, &y, TRUE)) {
6168             if (appData.highlightDragging) {
6169                 SetHighlights(x, y, -1, -1);
6170             } else {
6171                 ClearHighlights();
6172             }
6173             if (OKToStartUserMove(x, y)) {
6174                 fromX = x;
6175                 fromY = y;
6176                 MarkTargetSquares(0);
6177                 DragPieceBegin(xPix, yPix);
6178             }
6179             return;
6180            }
6181         }
6182         // ignore clicks on holdings
6183         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6184     }
6185
6186     if (clickType == Release && x == fromX && y == fromY) {
6187         DragPieceEnd(xPix, yPix);
6188         if (appData.animateDragging) {
6189             /* Undo animation damage if any */
6190             DrawPosition(FALSE, NULL);
6191         }
6192         if (second) {
6193             /* Second up/down in same square; just abort move */
6194             second = 0;
6195             fromX = fromY = -1;
6196             ClearHighlights();
6197             gotPremove = 0;
6198             ClearPremoveHighlights();
6199         } else {
6200             /* First upclick in same square; start click-click mode */
6201             SetHighlights(x, y, -1, -1);
6202         }
6203         return;
6204     }
6205
6206     /* we now have a different from- and (possibly off-board) to-square */
6207     /* Completed move */
6208     toX = x;
6209     toY = y;
6210     saveAnimate = appData.animate;
6211     if (clickType == Press) {
6212         /* Finish clickclick move */
6213         if (appData.animate || appData.highlightLastMove) {
6214             SetHighlights(fromX, fromY, toX, toY);
6215         } else {
6216             ClearHighlights();
6217         }
6218     } else {
6219         /* Finish drag move */
6220         if (appData.highlightLastMove) {
6221             SetHighlights(fromX, fromY, toX, toY);
6222         } else {
6223             ClearHighlights();
6224         }
6225         DragPieceEnd(xPix, yPix);
6226         /* Don't animate move and drag both */
6227         appData.animate = FALSE;
6228     }
6229
6230     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6231     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6232         ChessSquare piece = boards[currentMove][fromY][fromX];
6233         if(gameMode == EditPosition && piece != EmptySquare &&
6234            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6235             int n;
6236              
6237             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6238                 n = PieceToNumber(piece - (int)BlackPawn);
6239                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6240                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6241                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6242             } else
6243             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6244                 n = PieceToNumber(piece);
6245                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6246                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6247                 boards[currentMove][n][BOARD_WIDTH-2]++;
6248             }
6249             boards[currentMove][fromY][fromX] = EmptySquare;
6250         }
6251         ClearHighlights();
6252         fromX = fromY = -1;
6253         DrawPosition(TRUE, boards[currentMove]);
6254         return;
6255     }
6256
6257     // off-board moves should not be highlighted
6258     if(x < 0 || x < 0) ClearHighlights();
6259
6260     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6261         SetHighlights(fromX, fromY, toX, toY);
6262         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6263             // [HGM] super: promotion to captured piece selected from holdings
6264             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6265             promotionChoice = TRUE;
6266             // kludge follows to temporarily execute move on display, without promoting yet
6267             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6268             boards[currentMove][toY][toX] = p;
6269             DrawPosition(FALSE, boards[currentMove]);
6270             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6271             boards[currentMove][toY][toX] = q;
6272             DisplayMessage("Click in holdings to choose piece", "");
6273             return;
6274         }
6275         PromotionPopUp();
6276     } else {
6277         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6278         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6279         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6280         fromX = fromY = -1;
6281     }
6282     appData.animate = saveAnimate;
6283     if (appData.animate || appData.animateDragging) {
6284         /* Undo animation damage if needed */
6285         DrawPosition(FALSE, NULL);
6286     }
6287 }
6288
6289 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6290 {   // front-end-free part taken out of PieceMenuPopup
6291     int whichMenu; int xSqr, ySqr;
6292
6293     if(seekGraphUp) { // [HGM] seekgraph
6294         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6295         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6296         return -2;
6297     }
6298
6299     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6300          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6301         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6302         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6303         return -2;
6304     }
6305
6306     xSqr = EventToSquare(x, BOARD_WIDTH);
6307     ySqr = EventToSquare(y, BOARD_HEIGHT);
6308     if (action == Release) UnLoadPV(); // [HGM] pv
6309     if (action != Press) return -2; // return code to be ignored
6310     switch (gameMode) {
6311       case IcsExamining:
6312         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6313       case EditPosition:
6314         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6315         if (xSqr < 0 || ySqr < 0) return -1;\r
6316         whichMenu = 0; // edit-position menu
6317         break;
6318       case IcsObserving:
6319         if(!appData.icsEngineAnalyze) return -1;
6320       case IcsPlayingWhite:
6321       case IcsPlayingBlack:
6322         if(!appData.zippyPlay) goto noZip;
6323       case AnalyzeMode:
6324       case AnalyzeFile:
6325       case MachinePlaysWhite:
6326       case MachinePlaysBlack:
6327       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6328         if (!appData.dropMenu) {
6329           LoadPV(x, y);
6330           return 2; // flag front-end to grab mouse events
6331         }
6332         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6333            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6334       case EditGame:
6335       noZip:
6336         if (xSqr < 0 || ySqr < 0) return -1;
6337         if (!appData.dropMenu || appData.testLegality &&
6338             gameInfo.variant != VariantBughouse &&
6339             gameInfo.variant != VariantCrazyhouse) return -1;
6340         whichMenu = 1; // drop menu
6341         break;
6342       default:
6343         return -1;
6344     }
6345
6346     if (((*fromX = xSqr) < 0) ||
6347         ((*fromY = ySqr) < 0)) {
6348         *fromX = *fromY = -1;
6349         return -1;
6350     }
6351     if (flipView)
6352       *fromX = BOARD_WIDTH - 1 - *fromX;
6353     else
6354       *fromY = BOARD_HEIGHT - 1 - *fromY;
6355
6356     return whichMenu;
6357 }
6358
6359 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6360 {
6361 //    char * hint = lastHint;
6362     FrontEndProgramStats stats;
6363
6364     stats.which = cps == &first ? 0 : 1;
6365     stats.depth = cpstats->depth;
6366     stats.nodes = cpstats->nodes;
6367     stats.score = cpstats->score;
6368     stats.time = cpstats->time;
6369     stats.pv = cpstats->movelist;
6370     stats.hint = lastHint;
6371     stats.an_move_index = 0;
6372     stats.an_move_count = 0;
6373
6374     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6375         stats.hint = cpstats->move_name;
6376         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6377         stats.an_move_count = cpstats->nr_moves;
6378     }
6379
6380     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6381
6382     SetProgramStats( &stats );
6383 }
6384
6385 int
6386 Adjudicate(ChessProgramState *cps)
6387 {       // [HGM] some adjudications useful with buggy engines
6388         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6389         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6390         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6391         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6392         int k, count = 0; static int bare = 1;
6393         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6394         Boolean canAdjudicate = !appData.icsActive;
6395
6396         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6397         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6398             if( appData.testLegality )
6399             {   /* [HGM] Some more adjudications for obstinate engines */
6400                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6401                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6402                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6403                 static int moveCount = 6;
6404                 ChessMove result;
6405                 char *reason = NULL;
6406
6407                 /* Count what is on board. */
6408                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6409                 {   ChessSquare p = boards[forwardMostMove][i][j];
6410                     int m=i;
6411
6412                     switch((int) p)
6413                     {   /* count B,N,R and other of each side */
6414                         case WhiteKing:
6415                         case BlackKing:
6416                              NrK++; break; // [HGM] atomic: count Kings
6417                         case WhiteKnight:
6418                              NrWN++; break;
6419                         case WhiteBishop:
6420                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6421                              bishopsColor |= 1 << ((i^j)&1);
6422                              NrWB++; break;
6423                         case BlackKnight:
6424                              NrBN++; break;
6425                         case BlackBishop:
6426                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6427                              bishopsColor |= 1 << ((i^j)&1);
6428                              NrBB++; break;
6429                         case WhiteRook:
6430                              NrWR++; break;
6431                         case BlackRook:
6432                              NrBR++; break;
6433                         case WhiteQueen:
6434                              NrWQ++; break;
6435                         case BlackQueen:
6436                              NrBQ++; break;
6437                         case EmptySquare: 
6438                              break;
6439                         case BlackPawn:
6440                              m = 7-i;
6441                         case WhitePawn:
6442                              PawnAdvance += m; NrPawns++;
6443                     }
6444                     NrPieces += (p != EmptySquare);
6445                     NrW += ((int)p < (int)BlackPawn);
6446                     if(gameInfo.variant == VariantXiangqi && 
6447                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6448                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6449                         NrW -= ((int)p < (int)BlackPawn);
6450                     }
6451                 }
6452
6453                 /* Some material-based adjudications that have to be made before stalemate test */
6454                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6455                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6456                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6457                      if(canAdjudicate && appData.checkMates) {
6458                          if(engineOpponent)
6459                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6460                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6461                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6462                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6463                          return 1;
6464                      }
6465                 }
6466
6467                 /* Bare King in Shatranj (loses) or Losers (wins) */
6468                 if( NrW == 1 || NrPieces - NrW == 1) {
6469                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6470                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6471                      if(canAdjudicate && appData.checkMates) {
6472                          if(engineOpponent)
6473                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6474                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6475                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6476                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6477                          return 1;
6478                      }
6479                   } else
6480                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6481                   {    /* bare King */
6482                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6483                         if(canAdjudicate && appData.checkMates) {
6484                             /* but only adjudicate if adjudication enabled */
6485                             if(engineOpponent)
6486                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6487                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6488                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6489                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6490                             return 1;
6491                         }
6492                   }
6493                 } else bare = 1;
6494
6495
6496             // don't wait for engine to announce game end if we can judge ourselves
6497             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6498               case MT_CHECK:
6499                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6500                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6501                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6502                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6503                             checkCnt++;
6504                         if(checkCnt >= 2) {
6505                             reason = "Xboard adjudication: 3rd check";
6506                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6507                             break;
6508                         }
6509                     }
6510                 }
6511               case MT_NONE:
6512               default:
6513                 break;
6514               case MT_STALEMATE:
6515               case MT_STAINMATE:
6516                 reason = "Xboard adjudication: Stalemate";
6517                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6518                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6519                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6520                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6521                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6522                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6523                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6524                                                                         EP_CHECKMATE : EP_WINS);
6525                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6526                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6527                 }
6528                 break;
6529               case MT_CHECKMATE:
6530                 reason = "Xboard adjudication: Checkmate";
6531                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6532                 break;
6533             }
6534
6535                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6536                     case EP_STALEMATE:
6537                         result = GameIsDrawn; break;
6538                     case EP_CHECKMATE:
6539                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6540                     case EP_WINS:
6541                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6542                     default:
6543                         result = (ChessMove) 0;
6544                 }
6545                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6546                     if(engineOpponent)
6547                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6548                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6549                     GameEnds( result, reason, GE_XBOARD );
6550                     return 1;
6551                 }
6552
6553                 /* Next absolutely insufficient mating material. */
6554                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6555                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6556                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6557                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6558                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6559
6560                      /* always flag draws, for judging claims */
6561                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6562
6563                      if(canAdjudicate && appData.materialDraws) {
6564                          /* but only adjudicate them if adjudication enabled */
6565                          if(engineOpponent) {
6566                            SendToProgram("force\n", engineOpponent); // suppress reply
6567                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6568                          }
6569                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6570                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6571                          return 1;
6572                      }
6573                 }
6574
6575                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6576                 if(NrPieces == 4 && 
6577                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6578                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6579                    || NrWN==2 || NrBN==2     /* KNNK */
6580                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6581                   ) ) {
6582                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6583                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6584                           if(engineOpponent) {
6585                             SendToProgram("force\n", engineOpponent); // suppress reply
6586                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6587                           }
6588                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6589                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6590                           return 1;
6591                      }
6592                 } else moveCount = 6;
6593             }
6594         }
6595           
6596         if (appData.debugMode) { int i;
6597             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6598                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6599                     appData.drawRepeats);
6600             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6601               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6602             
6603         }
6604
6605         // Repetition draws and 50-move rule can be applied independently of legality testing
6606
6607                 /* Check for rep-draws */
6608                 count = 0;
6609                 for(k = forwardMostMove-2;
6610                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6611                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6612                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6613                     k-=2)
6614                 {   int rights=0;
6615                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6616                         /* compare castling rights */
6617                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6618                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6619                                 rights++; /* King lost rights, while rook still had them */
6620                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6621                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6622                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6623                                    rights++; /* but at least one rook lost them */
6624                         }
6625                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6626                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6627                                 rights++; 
6628                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6629                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6630                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6631                                    rights++;
6632                         }
6633                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6634                             && appData.drawRepeats > 1) {
6635                              /* adjudicate after user-specified nr of repeats */
6636                              if(engineOpponent) {
6637                                SendToProgram("force\n", engineOpponent); // suppress reply
6638                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6639                              }
6640                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6641                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6642                                 // [HGM] xiangqi: check for forbidden perpetuals
6643                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6644                                 for(m=forwardMostMove; m>k; m-=2) {
6645                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6646                                         ourPerpetual = 0; // the current mover did not always check
6647                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6648                                         hisPerpetual = 0; // the opponent did not always check
6649                                 }
6650                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6651                                                                         ourPerpetual, hisPerpetual);
6652                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6653                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6654                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6655                                     return 1;
6656                                 }
6657                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6658                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6659                                 // Now check for perpetual chases
6660                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6661                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6662                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6663                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6664                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6665                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6666                                         return 1;
6667                                     }
6668                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6669                                         break; // Abort repetition-checking loop.
6670                                 }
6671                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6672                              }
6673                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6674                              return 1;
6675                         }
6676                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6677                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6678                     }
6679                 }
6680
6681                 /* Now we test for 50-move draws. Determine ply count */
6682                 count = forwardMostMove;
6683                 /* look for last irreversble move */
6684                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6685                     count--;
6686                 /* if we hit starting position, add initial plies */
6687                 if( count == backwardMostMove )
6688                     count -= initialRulePlies;
6689                 count = forwardMostMove - count; 
6690                 if( count >= 100)
6691                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6692                          /* this is used to judge if draw claims are legal */
6693                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6694                          if(engineOpponent) {
6695                            SendToProgram("force\n", engineOpponent); // suppress reply
6696                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6697                          }
6698                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6699                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6700                          return 1;
6701                 }
6702
6703                 /* if draw offer is pending, treat it as a draw claim
6704                  * when draw condition present, to allow engines a way to
6705                  * claim draws before making their move to avoid a race
6706                  * condition occurring after their move
6707                  */
6708                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6709                          char *p = NULL;
6710                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6711                              p = "Draw claim: 50-move rule";
6712                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6713                              p = "Draw claim: 3-fold repetition";
6714                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6715                              p = "Draw claim: insufficient mating material";
6716                          if( p != NULL && canAdjudicate) {
6717                              if(engineOpponent) {
6718                                SendToProgram("force\n", engineOpponent); // suppress reply
6719                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6720                              }
6721                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6722                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6723                              return 1;
6724                          }
6725                 }
6726
6727                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6728                     if(engineOpponent) {
6729                       SendToProgram("force\n", engineOpponent); // suppress reply
6730                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6731                     }
6732                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6733                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6734                     return 1;
6735                 }
6736         return 0;
6737 }
6738
6739 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6740 {   // [HGM] book: this routine intercepts moves to simulate book replies
6741     char *bookHit = NULL;
6742
6743     //first determine if the incoming move brings opponent into his book
6744     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6745         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6746     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6747     if(bookHit != NULL && !cps->bookSuspend) {
6748         // make sure opponent is not going to reply after receiving move to book position
6749         SendToProgram("force\n", cps);
6750         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6751     }
6752     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6753     // now arrange restart after book miss
6754     if(bookHit) {
6755         // after a book hit we never send 'go', and the code after the call to this routine
6756         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6757         char buf[MSG_SIZ];
6758         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6759         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6760         SendToProgram(buf, cps);
6761         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6762     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6763         SendToProgram("go\n", cps);
6764         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6765     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6766         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6767             SendToProgram("go\n", cps); 
6768         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6769     }
6770     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6771 }
6772
6773 char *savedMessage;
6774 ChessProgramState *savedState;
6775 void DeferredBookMove(void)
6776 {
6777         if(savedState->lastPing != savedState->lastPong)
6778                     ScheduleDelayedEvent(DeferredBookMove, 10);
6779         else
6780         HandleMachineMove(savedMessage, savedState);
6781 }
6782
6783 void
6784 HandleMachineMove(message, cps)
6785      char *message;
6786      ChessProgramState *cps;
6787 {
6788     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6789     char realname[MSG_SIZ];
6790     int fromX, fromY, toX, toY;
6791     ChessMove moveType;
6792     char promoChar;
6793     char *p;
6794     int machineWhite;
6795     char *bookHit;
6796
6797     cps->userError = 0;
6798
6799 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6800     /*
6801      * Kludge to ignore BEL characters
6802      */
6803     while (*message == '\007') message++;
6804
6805     /*
6806      * [HGM] engine debug message: ignore lines starting with '#' character
6807      */
6808     if(cps->debug && *message == '#') return;
6809
6810     /*
6811      * Look for book output
6812      */
6813     if (cps == &first && bookRequested) {
6814         if (message[0] == '\t' || message[0] == ' ') {
6815             /* Part of the book output is here; append it */
6816             strcat(bookOutput, message);
6817             strcat(bookOutput, "  \n");
6818             return;
6819         } else if (bookOutput[0] != NULLCHAR) {
6820             /* All of book output has arrived; display it */
6821             char *p = bookOutput;
6822             while (*p != NULLCHAR) {
6823                 if (*p == '\t') *p = ' ';
6824                 p++;
6825             }
6826             DisplayInformation(bookOutput);
6827             bookRequested = FALSE;
6828             /* Fall through to parse the current output */
6829         }
6830     }
6831
6832     /*
6833      * Look for machine move.
6834      */
6835     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6836         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6837     {
6838         /* This method is only useful on engines that support ping */
6839         if (cps->lastPing != cps->lastPong) {
6840           if (gameMode == BeginningOfGame) {
6841             /* Extra move from before last new; ignore */
6842             if (appData.debugMode) {
6843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6844             }
6845           } else {
6846             if (appData.debugMode) {
6847                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6848                         cps->which, gameMode);
6849             }
6850
6851             SendToProgram("undo\n", cps);
6852           }
6853           return;
6854         }
6855
6856         switch (gameMode) {
6857           case BeginningOfGame:
6858             /* Extra move from before last reset; ignore */
6859             if (appData.debugMode) {
6860                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6861             }
6862             return;
6863
6864           case EndOfGame:
6865           case IcsIdle:
6866           default:
6867             /* Extra move after we tried to stop.  The mode test is
6868                not a reliable way of detecting this problem, but it's
6869                the best we can do on engines that don't support ping.
6870             */
6871             if (appData.debugMode) {
6872                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6873                         cps->which, gameMode);
6874             }
6875             SendToProgram("undo\n", cps);
6876             return;
6877
6878           case MachinePlaysWhite:
6879           case IcsPlayingWhite:
6880             machineWhite = TRUE;
6881             break;
6882
6883           case MachinePlaysBlack:
6884           case IcsPlayingBlack:
6885             machineWhite = FALSE;
6886             break;
6887
6888           case TwoMachinesPlay:
6889             machineWhite = (cps->twoMachinesColor[0] == 'w');
6890             break;
6891         }
6892         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6893             if (appData.debugMode) {
6894                 fprintf(debugFP,
6895                         "Ignoring move out of turn by %s, gameMode %d"
6896                         ", forwardMost %d\n",
6897                         cps->which, gameMode, forwardMostMove);
6898             }
6899             return;
6900         }
6901
6902     if (appData.debugMode) { int f = forwardMostMove;
6903         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6904                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6905                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6906     }
6907         if(cps->alphaRank) AlphaRank(machineMove, 4);
6908         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6909                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6910             /* Machine move could not be parsed; ignore it. */
6911             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6912                     machineMove, cps->which);
6913             DisplayError(buf1, 0);
6914             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6915                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6916             if (gameMode == TwoMachinesPlay) {
6917               GameEnds(machineWhite ? BlackWins : WhiteWins,
6918                        buf1, GE_XBOARD);
6919             }
6920             return;
6921         }
6922
6923         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6924         /* So we have to redo legality test with true e.p. status here,  */
6925         /* to make sure an illegal e.p. capture does not slip through,   */
6926         /* to cause a forfeit on a justified illegal-move complaint      */
6927         /* of the opponent.                                              */
6928         if( gameMode==TwoMachinesPlay && appData.testLegality
6929             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6930                                                               ) {
6931            ChessMove moveType;
6932            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6933                              fromY, fromX, toY, toX, promoChar);
6934             if (appData.debugMode) {
6935                 int i;
6936                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6937                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6938                 fprintf(debugFP, "castling rights\n");
6939             }
6940             if(moveType == IllegalMove) {
6941                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6942                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6943                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6944                            buf1, GE_XBOARD);
6945                 return;
6946            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6947            /* [HGM] Kludge to handle engines that send FRC-style castling
6948               when they shouldn't (like TSCP-Gothic) */
6949            switch(moveType) {
6950              case WhiteASideCastleFR:
6951              case BlackASideCastleFR:
6952                toX+=2;
6953                currentMoveString[2]++;
6954                break;
6955              case WhiteHSideCastleFR:
6956              case BlackHSideCastleFR:
6957                toX--;
6958                currentMoveString[2]--;
6959                break;
6960              default: ; // nothing to do, but suppresses warning of pedantic compilers
6961            }
6962         }
6963         hintRequested = FALSE;
6964         lastHint[0] = NULLCHAR;
6965         bookRequested = FALSE;
6966         /* Program may be pondering now */
6967         cps->maybeThinking = TRUE;
6968         if (cps->sendTime == 2) cps->sendTime = 1;
6969         if (cps->offeredDraw) cps->offeredDraw--;
6970
6971         /* currentMoveString is set as a side-effect of ParseOneMove */
6972         strcpy(machineMove, currentMoveString);
6973         strcat(machineMove, "\n");
6974         strcpy(moveList[forwardMostMove], machineMove);
6975
6976         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6977
6978         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6979         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6980             int count = 0;
6981
6982             while( count < adjudicateLossPlies ) {
6983                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6984
6985                 if( count & 1 ) {
6986                     score = -score; /* Flip score for winning side */
6987                 }
6988
6989                 if( score > adjudicateLossThreshold ) {
6990                     break;
6991                 }
6992
6993                 count++;
6994             }
6995
6996             if( count >= adjudicateLossPlies ) {
6997                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6998
6999                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7000                     "Xboard adjudication", 
7001                     GE_XBOARD );
7002
7003                 return;
7004             }
7005         }
7006
7007         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7008
7009 #if ZIPPY
7010         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7011             first.initDone) {
7012           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7013                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7014                 SendToICS("draw ");
7015                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7016           }
7017           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7018           ics_user_moved = 1;
7019           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7020                 char buf[3*MSG_SIZ];
7021
7022                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7023                         programStats.score / 100.,
7024                         programStats.depth,
7025                         programStats.time / 100.,
7026                         (unsigned int)programStats.nodes,
7027                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7028                         programStats.movelist);
7029                 SendToICS(buf);
7030 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7031           }
7032         }
7033 #endif
7034
7035         /* [AS] Save move info and clear stats for next move */
7036         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7037         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7038         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7039         ClearProgramStats();
7040         thinkOutput[0] = NULLCHAR;
7041         hiddenThinkOutputState = 0;
7042
7043         bookHit = NULL;
7044         if (gameMode == TwoMachinesPlay) {
7045             /* [HGM] relaying draw offers moved to after reception of move */
7046             /* and interpreting offer as claim if it brings draw condition */
7047             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7048                 SendToProgram("draw\n", cps->other);
7049             }
7050             if (cps->other->sendTime) {
7051                 SendTimeRemaining(cps->other,
7052                                   cps->other->twoMachinesColor[0] == 'w');
7053             }
7054             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7055             if (firstMove && !bookHit) {
7056                 firstMove = FALSE;
7057                 if (cps->other->useColors) {
7058                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7059                 }
7060                 SendToProgram("go\n", cps->other);
7061             }
7062             cps->other->maybeThinking = TRUE;
7063         }
7064
7065         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7066         
7067         if (!pausing && appData.ringBellAfterMoves) {
7068             RingBell();
7069         }
7070
7071         /* 
7072          * Reenable menu items that were disabled while
7073          * machine was thinking
7074          */
7075         if (gameMode != TwoMachinesPlay)
7076             SetUserThinkingEnables();
7077
7078         // [HGM] book: after book hit opponent has received move and is now in force mode
7079         // force the book reply into it, and then fake that it outputted this move by jumping
7080         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7081         if(bookHit) {
7082                 static char bookMove[MSG_SIZ]; // a bit generous?
7083
7084                 strcpy(bookMove, "move ");
7085                 strcat(bookMove, bookHit);
7086                 message = bookMove;
7087                 cps = cps->other;
7088                 programStats.nodes = programStats.depth = programStats.time = 
7089                 programStats.score = programStats.got_only_move = 0;
7090                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7091
7092                 if(cps->lastPing != cps->lastPong) {
7093                     savedMessage = message; // args for deferred call
7094                     savedState = cps;
7095                     ScheduleDelayedEvent(DeferredBookMove, 10);
7096                     return;
7097                 }
7098                 goto FakeBookMove;
7099         }
7100
7101         return;
7102     }
7103
7104     /* Set special modes for chess engines.  Later something general
7105      *  could be added here; for now there is just one kludge feature,
7106      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7107      *  when "xboard" is given as an interactive command.
7108      */
7109     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7110         cps->useSigint = FALSE;
7111         cps->useSigterm = FALSE;
7112     }
7113     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7114       ParseFeatures(message+8, cps);
7115       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7116     }
7117
7118     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7119      * want this, I was asked to put it in, and obliged.
7120      */
7121     if (!strncmp(message, "setboard ", 9)) {
7122         Board initial_position;
7123
7124         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7125
7126         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7127             DisplayError(_("Bad FEN received from engine"), 0);
7128             return ;
7129         } else {
7130            Reset(TRUE, FALSE);
7131            CopyBoard(boards[0], initial_position);
7132            initialRulePlies = FENrulePlies;
7133            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7134            else gameMode = MachinePlaysBlack;                 
7135            DrawPosition(FALSE, boards[currentMove]);
7136         }
7137         return;
7138     }
7139
7140     /*
7141      * Look for communication commands
7142      */
7143     if (!strncmp(message, "telluser ", 9)) {
7144         DisplayNote(message + 9);
7145         return;
7146     }
7147     if (!strncmp(message, "tellusererror ", 14)) {
7148         cps->userError = 1;
7149         DisplayError(message + 14, 0);
7150         return;
7151     }
7152     if (!strncmp(message, "tellopponent ", 13)) {
7153       if (appData.icsActive) {
7154         if (loggedOn) {
7155           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7156           SendToICS(buf1);
7157         }
7158       } else {
7159         DisplayNote(message + 13);
7160       }
7161       return;
7162     }
7163     if (!strncmp(message, "tellothers ", 11)) {
7164       if (appData.icsActive) {
7165         if (loggedOn) {
7166           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7167           SendToICS(buf1);
7168         }
7169       }
7170       return;
7171     }
7172     if (!strncmp(message, "tellall ", 8)) {
7173       if (appData.icsActive) {
7174         if (loggedOn) {
7175           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7176           SendToICS(buf1);
7177         }
7178       } else {
7179         DisplayNote(message + 8);
7180       }
7181       return;
7182     }
7183     if (strncmp(message, "warning", 7) == 0) {
7184         /* Undocumented feature, use tellusererror in new code */
7185         DisplayError(message, 0);
7186         return;
7187     }
7188     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7189         strcpy(realname, cps->tidy);
7190         strcat(realname, " query");
7191         AskQuestion(realname, buf2, buf1, cps->pr);
7192         return;
7193     }
7194     /* Commands from the engine directly to ICS.  We don't allow these to be 
7195      *  sent until we are logged on. Crafty kibitzes have been known to 
7196      *  interfere with the login process.
7197      */
7198     if (loggedOn) {
7199         if (!strncmp(message, "tellics ", 8)) {
7200             SendToICS(message + 8);
7201             SendToICS("\n");
7202             return;
7203         }
7204         if (!strncmp(message, "tellicsnoalias ", 15)) {
7205             SendToICS(ics_prefix);
7206             SendToICS(message + 15);
7207             SendToICS("\n");
7208             return;
7209         }
7210         /* The following are for backward compatibility only */
7211         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7212             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7213             SendToICS(ics_prefix);
7214             SendToICS(message);
7215             SendToICS("\n");
7216             return;
7217         }
7218     }
7219     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7220         return;
7221     }
7222     /*
7223      * If the move is illegal, cancel it and redraw the board.
7224      * Also deal with other error cases.  Matching is rather loose
7225      * here to accommodate engines written before the spec.
7226      */
7227     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7228         strncmp(message, "Error", 5) == 0) {
7229         if (StrStr(message, "name") || 
7230             StrStr(message, "rating") || StrStr(message, "?") ||
7231             StrStr(message, "result") || StrStr(message, "board") ||
7232             StrStr(message, "bk") || StrStr(message, "computer") ||
7233             StrStr(message, "variant") || StrStr(message, "hint") ||
7234             StrStr(message, "random") || StrStr(message, "depth") ||
7235             StrStr(message, "accepted")) {
7236             return;
7237         }
7238         if (StrStr(message, "protover")) {
7239           /* Program is responding to input, so it's apparently done
7240              initializing, and this error message indicates it is
7241              protocol version 1.  So we don't need to wait any longer
7242              for it to initialize and send feature commands. */
7243           FeatureDone(cps, 1);
7244           cps->protocolVersion = 1;
7245           return;
7246         }
7247         cps->maybeThinking = FALSE;
7248
7249         if (StrStr(message, "draw")) {
7250             /* Program doesn't have "draw" command */
7251             cps->sendDrawOffers = 0;
7252             return;
7253         }
7254         if (cps->sendTime != 1 &&
7255             (StrStr(message, "time") || StrStr(message, "otim"))) {
7256           /* Program apparently doesn't have "time" or "otim" command */
7257           cps->sendTime = 0;
7258           return;
7259         }
7260         if (StrStr(message, "analyze")) {
7261             cps->analysisSupport = FALSE;
7262             cps->analyzing = FALSE;
7263             Reset(FALSE, TRUE);
7264             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7265             DisplayError(buf2, 0);
7266             return;
7267         }
7268         if (StrStr(message, "(no matching move)st")) {
7269           /* Special kludge for GNU Chess 4 only */
7270           cps->stKludge = TRUE;
7271           SendTimeControl(cps, movesPerSession, timeControl,
7272                           timeIncrement, appData.searchDepth,
7273                           searchTime);
7274           return;
7275         }
7276         if (StrStr(message, "(no matching move)sd")) {
7277           /* Special kludge for GNU Chess 4 only */
7278           cps->sdKludge = TRUE;
7279           SendTimeControl(cps, movesPerSession, timeControl,
7280                           timeIncrement, appData.searchDepth,
7281                           searchTime);
7282           return;
7283         }
7284         if (!StrStr(message, "llegal")) {
7285             return;
7286         }
7287         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7288             gameMode == IcsIdle) return;
7289         if (forwardMostMove <= backwardMostMove) return;
7290         if (pausing) PauseEvent();
7291       if(appData.forceIllegal) {
7292             // [HGM] illegal: machine refused move; force position after move into it
7293           SendToProgram("force\n", cps);
7294           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7295                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7296                 // when black is to move, while there might be nothing on a2 or black
7297                 // might already have the move. So send the board as if white has the move.
7298                 // But first we must change the stm of the engine, as it refused the last move
7299                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7300                 if(WhiteOnMove(forwardMostMove)) {
7301                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7302                     SendBoard(cps, forwardMostMove); // kludgeless board
7303                 } else {
7304                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7305                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7306                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7307                 }
7308           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7309             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7310                  gameMode == TwoMachinesPlay)
7311               SendToProgram("go\n", cps);
7312             return;
7313       } else
7314         if (gameMode == PlayFromGameFile) {
7315             /* Stop reading this game file */
7316             gameMode = EditGame;
7317             ModeHighlight();
7318         }
7319         currentMove = forwardMostMove-1;
7320         DisplayMove(currentMove-1); /* before DisplayMoveError */
7321         SwitchClocks(forwardMostMove-1); // [HGM] race
7322         DisplayBothClocks();
7323         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7324                 parseList[currentMove], cps->which);
7325         DisplayMoveError(buf1);
7326         DrawPosition(FALSE, boards[currentMove]);
7327
7328         /* [HGM] illegal-move claim should forfeit game when Xboard */
7329         /* only passes fully legal moves                            */
7330         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7331             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7332                                 "False illegal-move claim", GE_XBOARD );
7333         }
7334         return;
7335     }
7336     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7337         /* Program has a broken "time" command that
7338            outputs a string not ending in newline.
7339            Don't use it. */
7340         cps->sendTime = 0;
7341     }
7342     
7343     /*
7344      * If chess program startup fails, exit with an error message.
7345      * Attempts to recover here are futile.
7346      */
7347     if ((StrStr(message, "unknown host") != NULL)
7348         || (StrStr(message, "No remote directory") != NULL)
7349         || (StrStr(message, "not found") != NULL)
7350         || (StrStr(message, "No such file") != NULL)
7351         || (StrStr(message, "can't alloc") != NULL)
7352         || (StrStr(message, "Permission denied") != NULL)) {
7353
7354         cps->maybeThinking = FALSE;
7355         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7356                 cps->which, cps->program, cps->host, message);
7357         RemoveInputSource(cps->isr);
7358         DisplayFatalError(buf1, 0, 1);
7359         return;
7360     }
7361     
7362     /* 
7363      * Look for hint output
7364      */
7365     if (sscanf(message, "Hint: %s", buf1) == 1) {
7366         if (cps == &first && hintRequested) {
7367             hintRequested = FALSE;
7368             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7369                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7370                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7371                                     PosFlags(forwardMostMove),
7372                                     fromY, fromX, toY, toX, promoChar, buf1);
7373                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7374                 DisplayInformation(buf2);
7375             } else {
7376                 /* Hint move could not be parsed!? */
7377               snprintf(buf2, sizeof(buf2),
7378                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7379                         buf1, cps->which);
7380                 DisplayError(buf2, 0);
7381             }
7382         } else {
7383             strcpy(lastHint, buf1);
7384         }
7385         return;
7386     }
7387
7388     /*
7389      * Ignore other messages if game is not in progress
7390      */
7391     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7392         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7393
7394     /*
7395      * look for win, lose, draw, or draw offer
7396      */
7397     if (strncmp(message, "1-0", 3) == 0) {
7398         char *p, *q, *r = "";
7399         p = strchr(message, '{');
7400         if (p) {
7401             q = strchr(p, '}');
7402             if (q) {
7403                 *q = NULLCHAR;
7404                 r = p + 1;
7405             }
7406         }
7407         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7408         return;
7409     } else if (strncmp(message, "0-1", 3) == 0) {
7410         char *p, *q, *r = "";
7411         p = strchr(message, '{');
7412         if (p) {
7413             q = strchr(p, '}');
7414             if (q) {
7415                 *q = NULLCHAR;
7416                 r = p + 1;
7417             }
7418         }
7419         /* Kludge for Arasan 4.1 bug */
7420         if (strcmp(r, "Black resigns") == 0) {
7421             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7422             return;
7423         }
7424         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7425         return;
7426     } else if (strncmp(message, "1/2", 3) == 0) {
7427         char *p, *q, *r = "";
7428         p = strchr(message, '{');
7429         if (p) {
7430             q = strchr(p, '}');
7431             if (q) {
7432                 *q = NULLCHAR;
7433                 r = p + 1;
7434             }
7435         }
7436             
7437         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7438         return;
7439
7440     } else if (strncmp(message, "White resign", 12) == 0) {
7441         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7442         return;
7443     } else if (strncmp(message, "Black resign", 12) == 0) {
7444         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7445         return;
7446     } else if (strncmp(message, "White matches", 13) == 0 ||
7447                strncmp(message, "Black matches", 13) == 0   ) {
7448         /* [HGM] ignore GNUShogi noises */
7449         return;
7450     } else if (strncmp(message, "White", 5) == 0 &&
7451                message[5] != '(' &&
7452                StrStr(message, "Black") == NULL) {
7453         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7454         return;
7455     } else if (strncmp(message, "Black", 5) == 0 &&
7456                message[5] != '(') {
7457         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7458         return;
7459     } else if (strcmp(message, "resign") == 0 ||
7460                strcmp(message, "computer resigns") == 0) {
7461         switch (gameMode) {
7462           case MachinePlaysBlack:
7463           case IcsPlayingBlack:
7464             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7465             break;
7466           case MachinePlaysWhite:
7467           case IcsPlayingWhite:
7468             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7469             break;
7470           case TwoMachinesPlay:
7471             if (cps->twoMachinesColor[0] == 'w')
7472               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7473             else
7474               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7475             break;
7476           default:
7477             /* can't happen */
7478             break;
7479         }
7480         return;
7481     } else if (strncmp(message, "opponent mates", 14) == 0) {
7482         switch (gameMode) {
7483           case MachinePlaysBlack:
7484           case IcsPlayingBlack:
7485             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7486             break;
7487           case MachinePlaysWhite:
7488           case IcsPlayingWhite:
7489             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7490             break;
7491           case TwoMachinesPlay:
7492             if (cps->twoMachinesColor[0] == 'w')
7493               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7494             else
7495               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7496             break;
7497           default:
7498             /* can't happen */
7499             break;
7500         }
7501         return;
7502     } else if (strncmp(message, "computer mates", 14) == 0) {
7503         switch (gameMode) {
7504           case MachinePlaysBlack:
7505           case IcsPlayingBlack:
7506             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7507             break;
7508           case MachinePlaysWhite:
7509           case IcsPlayingWhite:
7510             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7511             break;
7512           case TwoMachinesPlay:
7513             if (cps->twoMachinesColor[0] == 'w')
7514               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7515             else
7516               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7517             break;
7518           default:
7519             /* can't happen */
7520             break;
7521         }
7522         return;
7523     } else if (strncmp(message, "checkmate", 9) == 0) {
7524         if (WhiteOnMove(forwardMostMove)) {
7525             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7526         } else {
7527             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7528         }
7529         return;
7530     } else if (strstr(message, "Draw") != NULL ||
7531                strstr(message, "game is a draw") != NULL) {
7532         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7533         return;
7534     } else if (strstr(message, "offer") != NULL &&
7535                strstr(message, "draw") != NULL) {
7536 #if ZIPPY
7537         if (appData.zippyPlay && first.initDone) {
7538             /* Relay offer to ICS */
7539             SendToICS(ics_prefix);
7540             SendToICS("draw\n");
7541         }
7542 #endif
7543         cps->offeredDraw = 2; /* valid until this engine moves twice */
7544         if (gameMode == TwoMachinesPlay) {
7545             if (cps->other->offeredDraw) {
7546                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7547             /* [HGM] in two-machine mode we delay relaying draw offer      */
7548             /* until after we also have move, to see if it is really claim */
7549             }
7550         } else if (gameMode == MachinePlaysWhite ||
7551                    gameMode == MachinePlaysBlack) {
7552           if (userOfferedDraw) {
7553             DisplayInformation(_("Machine accepts your draw offer"));
7554             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7555           } else {
7556             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7557           }
7558         }
7559     }
7560
7561     
7562     /*
7563      * Look for thinking output
7564      */
7565     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7566           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7567                                 ) {
7568         int plylev, mvleft, mvtot, curscore, time;
7569         char mvname[MOVE_LEN];
7570         u64 nodes; // [DM]
7571         char plyext;
7572         int ignore = FALSE;
7573         int prefixHint = FALSE;
7574         mvname[0] = NULLCHAR;
7575
7576         switch (gameMode) {
7577           case MachinePlaysBlack:
7578           case IcsPlayingBlack:
7579             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7580             break;
7581           case MachinePlaysWhite:
7582           case IcsPlayingWhite:
7583             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7584             break;
7585           case AnalyzeMode:
7586           case AnalyzeFile:
7587             break;
7588           case IcsObserving: /* [DM] icsEngineAnalyze */
7589             if (!appData.icsEngineAnalyze) ignore = TRUE;
7590             break;
7591           case TwoMachinesPlay:
7592             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7593                 ignore = TRUE;
7594             }
7595             break;
7596           default:
7597             ignore = TRUE;
7598             break;
7599         }
7600
7601         if (!ignore) {
7602             buf1[0] = NULLCHAR;
7603             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7604                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7605
7606                 if (plyext != ' ' && plyext != '\t') {
7607                     time *= 100;
7608                 }
7609
7610                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7611                 if( cps->scoreIsAbsolute && 
7612                     ( gameMode == MachinePlaysBlack ||
7613                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7614                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7615                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7616                      !WhiteOnMove(currentMove)
7617                     ) )
7618                 {
7619                     curscore = -curscore;
7620                 }
7621
7622
7623                 programStats.depth = plylev;
7624                 programStats.nodes = nodes;
7625                 programStats.time = time;
7626                 programStats.score = curscore;
7627                 programStats.got_only_move = 0;
7628
7629                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7630                         int ticklen;
7631
7632                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7633                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7634                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7635                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7636                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7637                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7638                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7639                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7640                 }
7641
7642                 /* Buffer overflow protection */
7643                 if (buf1[0] != NULLCHAR) {
7644                     if (strlen(buf1) >= sizeof(programStats.movelist)
7645                         && appData.debugMode) {
7646                         fprintf(debugFP,
7647                                 "PV is too long; using the first %u bytes.\n",
7648                                 (unsigned) sizeof(programStats.movelist) - 1);
7649                     }
7650
7651                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7652                 } else {
7653                     sprintf(programStats.movelist, " no PV\n");
7654                 }
7655
7656                 if (programStats.seen_stat) {
7657                     programStats.ok_to_send = 1;
7658                 }
7659
7660                 if (strchr(programStats.movelist, '(') != NULL) {
7661                     programStats.line_is_book = 1;
7662                     programStats.nr_moves = 0;
7663                     programStats.moves_left = 0;
7664                 } else {
7665                     programStats.line_is_book = 0;
7666                 }
7667
7668                 SendProgramStatsToFrontend( cps, &programStats );
7669
7670                 /* 
7671                     [AS] Protect the thinkOutput buffer from overflow... this
7672                     is only useful if buf1 hasn't overflowed first!
7673                 */
7674                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7675                         plylev, 
7676                         (gameMode == TwoMachinesPlay ?
7677                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7678                         ((double) curscore) / 100.0,
7679                         prefixHint ? lastHint : "",
7680                         prefixHint ? " " : "" );
7681
7682                 if( buf1[0] != NULLCHAR ) {
7683                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7684
7685                     if( strlen(buf1) > max_len ) {
7686                         if( appData.debugMode) {
7687                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7688                         }
7689                         buf1[max_len+1] = '\0';
7690                     }
7691
7692                     strcat( thinkOutput, buf1 );
7693                 }
7694
7695                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7696                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7697                     DisplayMove(currentMove - 1);
7698                 }
7699                 return;
7700
7701             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7702                 /* crafty (9.25+) says "(only move) <move>"
7703                  * if there is only 1 legal move
7704                  */
7705                 sscanf(p, "(only move) %s", buf1);
7706                 sprintf(thinkOutput, "%s (only move)", buf1);
7707                 sprintf(programStats.movelist, "%s (only move)", buf1);
7708                 programStats.depth = 1;
7709                 programStats.nr_moves = 1;
7710                 programStats.moves_left = 1;
7711                 programStats.nodes = 1;
7712                 programStats.time = 1;
7713                 programStats.got_only_move = 1;
7714
7715                 /* Not really, but we also use this member to
7716                    mean "line isn't going to change" (Crafty
7717                    isn't searching, so stats won't change) */
7718                 programStats.line_is_book = 1;
7719
7720                 SendProgramStatsToFrontend( cps, &programStats );
7721                 
7722                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7723                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7724                     DisplayMove(currentMove - 1);
7725                 }
7726                 return;
7727             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7728                               &time, &nodes, &plylev, &mvleft,
7729                               &mvtot, mvname) >= 5) {
7730                 /* The stat01: line is from Crafty (9.29+) in response
7731                    to the "." command */
7732                 programStats.seen_stat = 1;
7733                 cps->maybeThinking = TRUE;
7734
7735                 if (programStats.got_only_move || !appData.periodicUpdates)
7736                   return;
7737
7738                 programStats.depth = plylev;
7739                 programStats.time = time;
7740                 programStats.nodes = nodes;
7741                 programStats.moves_left = mvleft;
7742                 programStats.nr_moves = mvtot;
7743                 strcpy(programStats.move_name, mvname);
7744                 programStats.ok_to_send = 1;
7745                 programStats.movelist[0] = '\0';
7746
7747                 SendProgramStatsToFrontend( cps, &programStats );
7748
7749                 return;
7750
7751             } else if (strncmp(message,"++",2) == 0) {
7752                 /* Crafty 9.29+ outputs this */
7753                 programStats.got_fail = 2;
7754                 return;
7755
7756             } else if (strncmp(message,"--",2) == 0) {
7757                 /* Crafty 9.29+ outputs this */
7758                 programStats.got_fail = 1;
7759                 return;
7760
7761             } else if (thinkOutput[0] != NULLCHAR &&
7762                        strncmp(message, "    ", 4) == 0) {
7763                 unsigned message_len;
7764
7765                 p = message;
7766                 while (*p && *p == ' ') p++;
7767
7768                 message_len = strlen( p );
7769
7770                 /* [AS] Avoid buffer overflow */
7771                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7772                     strcat(thinkOutput, " ");
7773                     strcat(thinkOutput, p);
7774                 }
7775
7776                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7777                     strcat(programStats.movelist, " ");
7778                     strcat(programStats.movelist, p);
7779                 }
7780
7781                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7782                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7783                     DisplayMove(currentMove - 1);
7784                 }
7785                 return;
7786             }
7787         }
7788         else {
7789             buf1[0] = NULLCHAR;
7790
7791             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7792                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7793             {
7794                 ChessProgramStats cpstats;
7795
7796                 if (plyext != ' ' && plyext != '\t') {
7797                     time *= 100;
7798                 }
7799
7800                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7801                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7802                     curscore = -curscore;
7803                 }
7804
7805                 cpstats.depth = plylev;
7806                 cpstats.nodes = nodes;
7807                 cpstats.time = time;
7808                 cpstats.score = curscore;
7809                 cpstats.got_only_move = 0;
7810                 cpstats.movelist[0] = '\0';
7811
7812                 if (buf1[0] != NULLCHAR) {
7813                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7814                 }
7815
7816                 cpstats.ok_to_send = 0;
7817                 cpstats.line_is_book = 0;
7818                 cpstats.nr_moves = 0;
7819                 cpstats.moves_left = 0;
7820
7821                 SendProgramStatsToFrontend( cps, &cpstats );
7822             }
7823         }
7824     }
7825 }
7826
7827
7828 /* Parse a game score from the character string "game", and
7829    record it as the history of the current game.  The game
7830    score is NOT assumed to start from the standard position. 
7831    The display is not updated in any way.
7832    */
7833 void
7834 ParseGameHistory(game)
7835      char *game;
7836 {
7837     ChessMove moveType;
7838     int fromX, fromY, toX, toY, boardIndex;
7839     char promoChar;
7840     char *p, *q;
7841     char buf[MSG_SIZ];
7842
7843     if (appData.debugMode)
7844       fprintf(debugFP, "Parsing game history: %s\n", game);
7845
7846     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7847     gameInfo.site = StrSave(appData.icsHost);
7848     gameInfo.date = PGNDate();
7849     gameInfo.round = StrSave("-");
7850
7851     /* Parse out names of players */
7852     while (*game == ' ') game++;
7853     p = buf;
7854     while (*game != ' ') *p++ = *game++;
7855     *p = NULLCHAR;
7856     gameInfo.white = StrSave(buf);
7857     while (*game == ' ') game++;
7858     p = buf;
7859     while (*game != ' ' && *game != '\n') *p++ = *game++;
7860     *p = NULLCHAR;
7861     gameInfo.black = StrSave(buf);
7862
7863     /* Parse moves */
7864     boardIndex = blackPlaysFirst ? 1 : 0;
7865     yynewstr(game);
7866     for (;;) {
7867         yyboardindex = boardIndex;
7868         moveType = (ChessMove) yylex();
7869         switch (moveType) {
7870           case IllegalMove:             /* maybe suicide chess, etc. */
7871   if (appData.debugMode) {
7872     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7873     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7874     setbuf(debugFP, NULL);
7875   }
7876           case WhitePromotionChancellor:
7877           case BlackPromotionChancellor:
7878           case WhitePromotionArchbishop:
7879           case BlackPromotionArchbishop:
7880           case WhitePromotionQueen:
7881           case BlackPromotionQueen:
7882           case WhitePromotionRook:
7883           case BlackPromotionRook:
7884           case WhitePromotionBishop:
7885           case BlackPromotionBishop:
7886           case WhitePromotionKnight:
7887           case BlackPromotionKnight:
7888           case WhitePromotionKing:
7889           case BlackPromotionKing:
7890           case NormalMove:
7891           case WhiteCapturesEnPassant:
7892           case BlackCapturesEnPassant:
7893           case WhiteKingSideCastle:
7894           case WhiteQueenSideCastle:
7895           case BlackKingSideCastle:
7896           case BlackQueenSideCastle:
7897           case WhiteKingSideCastleWild:
7898           case WhiteQueenSideCastleWild:
7899           case BlackKingSideCastleWild:
7900           case BlackQueenSideCastleWild:
7901           /* PUSH Fabien */
7902           case WhiteHSideCastleFR:
7903           case WhiteASideCastleFR:
7904           case BlackHSideCastleFR:
7905           case BlackASideCastleFR:
7906           /* POP Fabien */
7907             fromX = currentMoveString[0] - AAA;
7908             fromY = currentMoveString[1] - ONE;
7909             toX = currentMoveString[2] - AAA;
7910             toY = currentMoveString[3] - ONE;
7911             promoChar = currentMoveString[4];
7912             break;
7913           case WhiteDrop:
7914           case BlackDrop:
7915             fromX = moveType == WhiteDrop ?
7916               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7917             (int) CharToPiece(ToLower(currentMoveString[0]));
7918             fromY = DROP_RANK;
7919             toX = currentMoveString[2] - AAA;
7920             toY = currentMoveString[3] - ONE;
7921             promoChar = NULLCHAR;
7922             break;
7923           case AmbiguousMove:
7924             /* bug? */
7925             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7926   if (appData.debugMode) {
7927     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7928     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7929     setbuf(debugFP, NULL);
7930   }
7931             DisplayError(buf, 0);
7932             return;
7933           case ImpossibleMove:
7934             /* bug? */
7935             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7936   if (appData.debugMode) {
7937     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7938     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7939     setbuf(debugFP, NULL);
7940   }
7941             DisplayError(buf, 0);
7942             return;
7943           case (ChessMove) 0:   /* end of file */
7944             if (boardIndex < backwardMostMove) {
7945                 /* Oops, gap.  How did that happen? */
7946                 DisplayError(_("Gap in move list"), 0);
7947                 return;
7948             }
7949             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7950             if (boardIndex > forwardMostMove) {
7951                 forwardMostMove = boardIndex;
7952             }
7953             return;
7954           case ElapsedTime:
7955             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7956                 strcat(parseList[boardIndex-1], " ");
7957                 strcat(parseList[boardIndex-1], yy_text);
7958             }
7959             continue;
7960           case Comment:
7961           case PGNTag:
7962           case NAG:
7963           default:
7964             /* ignore */
7965             continue;
7966           case WhiteWins:
7967           case BlackWins:
7968           case GameIsDrawn:
7969           case GameUnfinished:
7970             if (gameMode == IcsExamining) {
7971                 if (boardIndex < backwardMostMove) {
7972                     /* Oops, gap.  How did that happen? */
7973                     return;
7974                 }
7975                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7976                 return;
7977             }
7978             gameInfo.result = moveType;
7979             p = strchr(yy_text, '{');
7980             if (p == NULL) p = strchr(yy_text, '(');
7981             if (p == NULL) {
7982                 p = yy_text;
7983                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7984             } else {
7985                 q = strchr(p, *p == '{' ? '}' : ')');
7986                 if (q != NULL) *q = NULLCHAR;
7987                 p++;
7988             }
7989             gameInfo.resultDetails = StrSave(p);
7990             continue;
7991         }
7992         if (boardIndex >= forwardMostMove &&
7993             !(gameMode == IcsObserving && ics_gamenum == -1)) {
7994             backwardMostMove = blackPlaysFirst ? 1 : 0;
7995             return;
7996         }
7997         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7998                                  fromY, fromX, toY, toX, promoChar,
7999                                  parseList[boardIndex]);
8000         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8001         /* currentMoveString is set as a side-effect of yylex */
8002         strcpy(moveList[boardIndex], currentMoveString);
8003         strcat(moveList[boardIndex], "\n");
8004         boardIndex++;
8005         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8006         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8007           case MT_NONE:
8008           case MT_STALEMATE:
8009           default:
8010             break;
8011           case MT_CHECK:
8012             if(gameInfo.variant != VariantShogi)
8013                 strcat(parseList[boardIndex - 1], "+");
8014             break;
8015           case MT_CHECKMATE:
8016           case MT_STAINMATE:
8017             strcat(parseList[boardIndex - 1], "#");
8018             break;
8019         }
8020     }
8021 }
8022
8023
8024 /* Apply a move to the given board  */
8025 void
8026 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8027      int fromX, fromY, toX, toY;
8028      int promoChar;
8029      Board board;
8030 {
8031   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8032   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8033
8034     /* [HGM] compute & store e.p. status and castling rights for new position */
8035     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8036     { int i;
8037
8038       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8039       oldEP = (signed char)board[EP_STATUS];
8040       board[EP_STATUS] = EP_NONE;
8041
8042       if( board[toY][toX] != EmptySquare ) 
8043            board[EP_STATUS] = EP_CAPTURE;  
8044
8045       if( board[fromY][fromX] == WhitePawn ) {
8046            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8047                board[EP_STATUS] = EP_PAWN_MOVE;
8048            if( toY-fromY==2) {
8049                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8050                         gameInfo.variant != VariantBerolina || toX < fromX)
8051                       board[EP_STATUS] = toX | berolina;
8052                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8053                         gameInfo.variant != VariantBerolina || toX > fromX) 
8054                       board[EP_STATUS] = toX;
8055            }
8056       } else 
8057       if( board[fromY][fromX] == BlackPawn ) {
8058            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8059                board[EP_STATUS] = EP_PAWN_MOVE; 
8060            if( toY-fromY== -2) {
8061                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8062                         gameInfo.variant != VariantBerolina || toX < fromX)
8063                       board[EP_STATUS] = toX | berolina;
8064                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8065                         gameInfo.variant != VariantBerolina || toX > fromX) 
8066                       board[EP_STATUS] = toX;
8067            }
8068        }
8069
8070        for(i=0; i<nrCastlingRights; i++) {
8071            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8072               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8073              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8074        }
8075
8076     }
8077
8078   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8079   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8080        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8081          
8082   if (fromX == toX && fromY == toY) return;
8083
8084   if (fromY == DROP_RANK) {
8085         /* must be first */
8086         piece = board[toY][toX] = (ChessSquare) fromX;
8087   } else {
8088      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8089      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8090      if(gameInfo.variant == VariantKnightmate)
8091          king += (int) WhiteUnicorn - (int) WhiteKing;
8092
8093     /* Code added by Tord: */
8094     /* FRC castling assumed when king captures friendly rook. */
8095     if (board[fromY][fromX] == WhiteKing &&
8096              board[toY][toX] == WhiteRook) {
8097       board[fromY][fromX] = EmptySquare;
8098       board[toY][toX] = EmptySquare;
8099       if(toX > fromX) {
8100         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8101       } else {
8102         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8103       }
8104     } else if (board[fromY][fromX] == BlackKing &&
8105                board[toY][toX] == BlackRook) {
8106       board[fromY][fromX] = EmptySquare;
8107       board[toY][toX] = EmptySquare;
8108       if(toX > fromX) {
8109         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8110       } else {
8111         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8112       }
8113     /* End of code added by Tord */
8114
8115     } else if (board[fromY][fromX] == king
8116         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8117         && toY == fromY && toX > fromX+1) {
8118         board[fromY][fromX] = EmptySquare;
8119         board[toY][toX] = king;
8120         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8121         board[fromY][BOARD_RGHT-1] = EmptySquare;
8122     } else if (board[fromY][fromX] == king
8123         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8124                && toY == fromY && toX < fromX-1) {
8125         board[fromY][fromX] = EmptySquare;
8126         board[toY][toX] = king;
8127         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8128         board[fromY][BOARD_LEFT] = EmptySquare;
8129     } else if (board[fromY][fromX] == WhitePawn
8130                && toY >= BOARD_HEIGHT-promoRank
8131                && gameInfo.variant != VariantXiangqi
8132                ) {
8133         /* white pawn promotion */
8134         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8135         if (board[toY][toX] == EmptySquare) {
8136             board[toY][toX] = WhiteQueen;
8137         }
8138         if(gameInfo.variant==VariantBughouse ||
8139            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8140             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8141         board[fromY][fromX] = EmptySquare;
8142     } else if ((fromY == BOARD_HEIGHT-4)
8143                && (toX != fromX)
8144                && gameInfo.variant != VariantXiangqi
8145                && gameInfo.variant != VariantBerolina
8146                && (board[fromY][fromX] == WhitePawn)
8147                && (board[toY][toX] == EmptySquare)) {
8148         board[fromY][fromX] = EmptySquare;
8149         board[toY][toX] = WhitePawn;
8150         captured = board[toY - 1][toX];
8151         board[toY - 1][toX] = EmptySquare;
8152     } else if ((fromY == BOARD_HEIGHT-4)
8153                && (toX == fromX)
8154                && gameInfo.variant == VariantBerolina
8155                && (board[fromY][fromX] == WhitePawn)
8156                && (board[toY][toX] == EmptySquare)) {
8157         board[fromY][fromX] = EmptySquare;
8158         board[toY][toX] = WhitePawn;
8159         if(oldEP & EP_BEROLIN_A) {
8160                 captured = board[fromY][fromX-1];
8161                 board[fromY][fromX-1] = EmptySquare;
8162         }else{  captured = board[fromY][fromX+1];
8163                 board[fromY][fromX+1] = EmptySquare;
8164         }
8165     } else if (board[fromY][fromX] == king
8166         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8167                && toY == fromY && toX > fromX+1) {
8168         board[fromY][fromX] = EmptySquare;
8169         board[toY][toX] = king;
8170         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8171         board[fromY][BOARD_RGHT-1] = EmptySquare;
8172     } else if (board[fromY][fromX] == king
8173         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8174                && toY == fromY && toX < fromX-1) {
8175         board[fromY][fromX] = EmptySquare;
8176         board[toY][toX] = king;
8177         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8178         board[fromY][BOARD_LEFT] = EmptySquare;
8179     } else if (fromY == 7 && fromX == 3
8180                && board[fromY][fromX] == BlackKing
8181                && toY == 7 && toX == 5) {
8182         board[fromY][fromX] = EmptySquare;
8183         board[toY][toX] = BlackKing;
8184         board[fromY][7] = EmptySquare;
8185         board[toY][4] = BlackRook;
8186     } else if (fromY == 7 && fromX == 3
8187                && board[fromY][fromX] == BlackKing
8188                && toY == 7 && toX == 1) {
8189         board[fromY][fromX] = EmptySquare;
8190         board[toY][toX] = BlackKing;
8191         board[fromY][0] = EmptySquare;
8192         board[toY][2] = BlackRook;
8193     } else if (board[fromY][fromX] == BlackPawn
8194                && toY < promoRank
8195                && gameInfo.variant != VariantXiangqi
8196                ) {
8197         /* black pawn promotion */
8198         board[toY][toX] = CharToPiece(ToLower(promoChar));
8199         if (board[toY][toX] == EmptySquare) {
8200             board[toY][toX] = BlackQueen;
8201         }
8202         if(gameInfo.variant==VariantBughouse ||
8203            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8204             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8205         board[fromY][fromX] = EmptySquare;
8206     } else if ((fromY == 3)
8207                && (toX != fromX)
8208                && gameInfo.variant != VariantXiangqi
8209                && gameInfo.variant != VariantBerolina
8210                && (board[fromY][fromX] == BlackPawn)
8211                && (board[toY][toX] == EmptySquare)) {
8212         board[fromY][fromX] = EmptySquare;
8213         board[toY][toX] = BlackPawn;
8214         captured = board[toY + 1][toX];
8215         board[toY + 1][toX] = EmptySquare;
8216     } else if ((fromY == 3)
8217                && (toX == fromX)
8218                && gameInfo.variant == VariantBerolina
8219                && (board[fromY][fromX] == BlackPawn)
8220                && (board[toY][toX] == EmptySquare)) {
8221         board[fromY][fromX] = EmptySquare;
8222         board[toY][toX] = BlackPawn;
8223         if(oldEP & EP_BEROLIN_A) {
8224                 captured = board[fromY][fromX-1];
8225                 board[fromY][fromX-1] = EmptySquare;
8226         }else{  captured = board[fromY][fromX+1];
8227                 board[fromY][fromX+1] = EmptySquare;
8228         }
8229     } else {
8230         board[toY][toX] = board[fromY][fromX];
8231         board[fromY][fromX] = EmptySquare;
8232     }
8233
8234     /* [HGM] now we promote for Shogi, if needed */
8235     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8236         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8237   }
8238
8239     if (gameInfo.holdingsWidth != 0) {
8240
8241       /* !!A lot more code needs to be written to support holdings  */
8242       /* [HGM] OK, so I have written it. Holdings are stored in the */
8243       /* penultimate board files, so they are automaticlly stored   */
8244       /* in the game history.                                       */
8245       if (fromY == DROP_RANK) {
8246         /* Delete from holdings, by decreasing count */
8247         /* and erasing image if necessary            */
8248         p = (int) fromX;
8249         if(p < (int) BlackPawn) { /* white drop */
8250              p -= (int)WhitePawn;
8251                  p = PieceToNumber((ChessSquare)p);
8252              if(p >= gameInfo.holdingsSize) p = 0;
8253              if(--board[p][BOARD_WIDTH-2] <= 0)
8254                   board[p][BOARD_WIDTH-1] = EmptySquare;
8255              if((int)board[p][BOARD_WIDTH-2] < 0)
8256                         board[p][BOARD_WIDTH-2] = 0;
8257         } else {                  /* black drop */
8258              p -= (int)BlackPawn;
8259                  p = PieceToNumber((ChessSquare)p);
8260              if(p >= gameInfo.holdingsSize) p = 0;
8261              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8262                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8263              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8264                         board[BOARD_HEIGHT-1-p][1] = 0;
8265         }
8266       }
8267       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8268           && gameInfo.variant != VariantBughouse        ) {
8269         /* [HGM] holdings: Add to holdings, if holdings exist */
8270         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8271                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8272                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8273         }
8274         p = (int) captured;
8275         if (p >= (int) BlackPawn) {
8276           p -= (int)BlackPawn;
8277           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8278                   /* in Shogi restore piece to its original  first */
8279                   captured = (ChessSquare) (DEMOTED captured);
8280                   p = DEMOTED p;
8281           }
8282           p = PieceToNumber((ChessSquare)p);
8283           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8284           board[p][BOARD_WIDTH-2]++;
8285           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8286         } else {
8287           p -= (int)WhitePawn;
8288           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8289                   captured = (ChessSquare) (DEMOTED captured);
8290                   p = DEMOTED p;
8291           }
8292           p = PieceToNumber((ChessSquare)p);
8293           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8294           board[BOARD_HEIGHT-1-p][1]++;
8295           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8296         }
8297       }
8298     } else if (gameInfo.variant == VariantAtomic) {
8299       if (captured != EmptySquare) {
8300         int y, x;
8301         for (y = toY-1; y <= toY+1; y++) {
8302           for (x = toX-1; x <= toX+1; x++) {
8303             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8304                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8305               board[y][x] = EmptySquare;
8306             }
8307           }
8308         }
8309         board[toY][toX] = EmptySquare;
8310       }
8311     }
8312     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8313         /* [HGM] Shogi promotions */
8314         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8315     }
8316
8317     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8318                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8319         // [HGM] superchess: take promotion piece out of holdings
8320         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8321         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8322             if(!--board[k][BOARD_WIDTH-2])
8323                 board[k][BOARD_WIDTH-1] = EmptySquare;
8324         } else {
8325             if(!--board[BOARD_HEIGHT-1-k][1])
8326                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8327         }
8328     }
8329
8330 }
8331
8332 /* Updates forwardMostMove */
8333 void
8334 MakeMove(fromX, fromY, toX, toY, promoChar)
8335      int fromX, fromY, toX, toY;
8336      int promoChar;
8337 {
8338 //    forwardMostMove++; // [HGM] bare: moved downstream
8339
8340     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8341         int timeLeft; static int lastLoadFlag=0; int king, piece;
8342         piece = boards[forwardMostMove][fromY][fromX];
8343         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8344         if(gameInfo.variant == VariantKnightmate)
8345             king += (int) WhiteUnicorn - (int) WhiteKing;
8346         if(forwardMostMove == 0) {
8347             if(blackPlaysFirst) 
8348                 fprintf(serverMoves, "%s;", second.tidy);
8349             fprintf(serverMoves, "%s;", first.tidy);
8350             if(!blackPlaysFirst) 
8351                 fprintf(serverMoves, "%s;", second.tidy);
8352         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8353         lastLoadFlag = loadFlag;
8354         // print base move
8355         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8356         // print castling suffix
8357         if( toY == fromY && piece == king ) {
8358             if(toX-fromX > 1)
8359                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8360             if(fromX-toX >1)
8361                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8362         }
8363         // e.p. suffix
8364         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8365              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8366              boards[forwardMostMove][toY][toX] == EmptySquare
8367              && fromX != toX )
8368                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8369         // promotion suffix
8370         if(promoChar != NULLCHAR)
8371                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8372         if(!loadFlag) {
8373             fprintf(serverMoves, "/%d/%d",
8374                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8375             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8376             else                      timeLeft = blackTimeRemaining/1000;
8377             fprintf(serverMoves, "/%d", timeLeft);
8378         }
8379         fflush(serverMoves);
8380     }
8381
8382     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8384                         0, 1);
8385       return;
8386     }
8387     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8388     if (commentList[forwardMostMove+1] != NULL) {
8389         free(commentList[forwardMostMove+1]);
8390         commentList[forwardMostMove+1] = NULL;
8391     }
8392     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8393     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8394     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8395     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8396     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8397     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8398     gameInfo.result = GameUnfinished;
8399     if (gameInfo.resultDetails != NULL) {
8400         free(gameInfo.resultDetails);
8401         gameInfo.resultDetails = NULL;
8402     }
8403     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8404                               moveList[forwardMostMove - 1]);
8405     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8406                              PosFlags(forwardMostMove - 1),
8407                              fromY, fromX, toY, toX, promoChar,
8408                              parseList[forwardMostMove - 1]);
8409     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8410       case MT_NONE:
8411       case MT_STALEMATE:
8412       default:
8413         break;
8414       case MT_CHECK:
8415         if(gameInfo.variant != VariantShogi)
8416             strcat(parseList[forwardMostMove - 1], "+");
8417         break;
8418       case MT_CHECKMATE:
8419       case MT_STAINMATE:
8420         strcat(parseList[forwardMostMove - 1], "#");
8421         break;
8422     }
8423     if (appData.debugMode) {
8424         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8425     }
8426
8427 }
8428
8429 /* Updates currentMove if not pausing */
8430 void
8431 ShowMove(fromX, fromY, toX, toY)
8432 {
8433     int instant = (gameMode == PlayFromGameFile) ?
8434         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8435     if(appData.noGUI) return;
8436     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8437         if (!instant) {
8438             if (forwardMostMove == currentMove + 1) {
8439                 AnimateMove(boards[forwardMostMove - 1],
8440                             fromX, fromY, toX, toY);
8441             }
8442             if (appData.highlightLastMove) {
8443                 SetHighlights(fromX, fromY, toX, toY);
8444             }
8445         }
8446         currentMove = forwardMostMove;
8447     }
8448
8449     if (instant) return;
8450
8451     DisplayMove(currentMove - 1);
8452     DrawPosition(FALSE, boards[currentMove]);
8453     DisplayBothClocks();
8454     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8455 }
8456
8457 void SendEgtPath(ChessProgramState *cps)
8458 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8459         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8460
8461         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8462
8463         while(*p) {
8464             char c, *q = name+1, *r, *s;
8465
8466             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8467             while(*p && *p != ',') *q++ = *p++;
8468             *q++ = ':'; *q = 0;
8469             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8470                 strcmp(name, ",nalimov:") == 0 ) {
8471                 // take nalimov path from the menu-changeable option first, if it is defined
8472                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8473                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8474             } else
8475             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8476                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8477                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8478                 s = r = StrStr(s, ":") + 1; // beginning of path info
8479                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8480                 c = *r; *r = 0;             // temporarily null-terminate path info
8481                     *--q = 0;               // strip of trailig ':' from name
8482                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8483                 *r = c;
8484                 SendToProgram(buf,cps);     // send egtbpath command for this format
8485             }
8486             if(*p == ',') p++; // read away comma to position for next format name
8487         }
8488 }
8489
8490 void
8491 InitChessProgram(cps, setup)
8492      ChessProgramState *cps;
8493      int setup; /* [HGM] needed to setup FRC opening position */
8494 {
8495     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8496     if (appData.noChessProgram) return;
8497     hintRequested = FALSE;
8498     bookRequested = FALSE;
8499
8500     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8501     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8502     if(cps->memSize) { /* [HGM] memory */
8503         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8504         SendToProgram(buf, cps);
8505     }
8506     SendEgtPath(cps); /* [HGM] EGT */
8507     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8508         sprintf(buf, "cores %d\n", appData.smpCores);
8509         SendToProgram(buf, cps);
8510     }
8511
8512     SendToProgram(cps->initString, cps);
8513     if (gameInfo.variant != VariantNormal &&
8514         gameInfo.variant != VariantLoadable
8515         /* [HGM] also send variant if board size non-standard */
8516         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8517                                             ) {
8518       char *v = VariantName(gameInfo.variant);
8519       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8520         /* [HGM] in protocol 1 we have to assume all variants valid */
8521         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8522         DisplayFatalError(buf, 0, 1);
8523         return;
8524       }
8525
8526       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8527       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8528       if( gameInfo.variant == VariantXiangqi )
8529            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8530       if( gameInfo.variant == VariantShogi )
8531            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8532       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8533            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8534       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8535                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8536            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8537       if( gameInfo.variant == VariantCourier )
8538            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8539       if( gameInfo.variant == VariantSuper )
8540            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8541       if( gameInfo.variant == VariantGreat )
8542            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8543
8544       if(overruled) {
8545            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8546                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8547            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8548            if(StrStr(cps->variants, b) == NULL) { 
8549                // specific sized variant not known, check if general sizing allowed
8550                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8551                    if(StrStr(cps->variants, "boardsize") == NULL) {
8552                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8553                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8554                        DisplayFatalError(buf, 0, 1);
8555                        return;
8556                    }
8557                    /* [HGM] here we really should compare with the maximum supported board size */
8558                }
8559            }
8560       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8561       sprintf(buf, "variant %s\n", b);
8562       SendToProgram(buf, cps);
8563     }
8564     currentlyInitializedVariant = gameInfo.variant;
8565
8566     /* [HGM] send opening position in FRC to first engine */
8567     if(setup) {
8568           SendToProgram("force\n", cps);
8569           SendBoard(cps, 0);
8570           /* engine is now in force mode! Set flag to wake it up after first move. */
8571           setboardSpoiledMachineBlack = 1;
8572     }
8573
8574     if (cps->sendICS) {
8575       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8576       SendToProgram(buf, cps);
8577     }
8578     cps->maybeThinking = FALSE;
8579     cps->offeredDraw = 0;
8580     if (!appData.icsActive) {
8581         SendTimeControl(cps, movesPerSession, timeControl,
8582                         timeIncrement, appData.searchDepth,
8583                         searchTime);
8584     }
8585     if (appData.showThinking 
8586         // [HGM] thinking: four options require thinking output to be sent
8587         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8588                                 ) {
8589         SendToProgram("post\n", cps);
8590     }
8591     SendToProgram("hard\n", cps);
8592     if (!appData.ponderNextMove) {
8593         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8594            it without being sure what state we are in first.  "hard"
8595            is not a toggle, so that one is OK.
8596          */
8597         SendToProgram("easy\n", cps);
8598     }
8599     if (cps->usePing) {
8600       sprintf(buf, "ping %d\n", ++cps->lastPing);
8601       SendToProgram(buf, cps);
8602     }
8603     cps->initDone = TRUE;
8604 }   
8605
8606
8607 void
8608 StartChessProgram(cps)
8609      ChessProgramState *cps;
8610 {
8611     char buf[MSG_SIZ];
8612     int err;
8613
8614     if (appData.noChessProgram) return;
8615     cps->initDone = FALSE;
8616
8617     if (strcmp(cps->host, "localhost") == 0) {
8618         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8619     } else if (*appData.remoteShell == NULLCHAR) {
8620         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8621     } else {
8622         if (*appData.remoteUser == NULLCHAR) {
8623           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8624                     cps->program);
8625         } else {
8626           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8627                     cps->host, appData.remoteUser, cps->program);
8628         }
8629         err = StartChildProcess(buf, "", &cps->pr);
8630     }
8631     
8632     if (err != 0) {
8633         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8634         DisplayFatalError(buf, err, 1);
8635         cps->pr = NoProc;
8636         cps->isr = NULL;
8637         return;
8638     }
8639     
8640     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8641     if (cps->protocolVersion > 1) {
8642       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8643       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8644       cps->comboCnt = 0;  //                and values of combo boxes
8645       SendToProgram(buf, cps);
8646     } else {
8647       SendToProgram("xboard\n", cps);
8648     }
8649 }
8650
8651
8652 void
8653 TwoMachinesEventIfReady P((void))
8654 {
8655   if (first.lastPing != first.lastPong) {
8656     DisplayMessage("", _("Waiting for first chess program"));
8657     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8658     return;
8659   }
8660   if (second.lastPing != second.lastPong) {
8661     DisplayMessage("", _("Waiting for second chess program"));
8662     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8663     return;
8664   }
8665   ThawUI();
8666   TwoMachinesEvent();
8667 }
8668
8669 void
8670 NextMatchGame P((void))
8671 {
8672     int index; /* [HGM] autoinc: step load index during match */
8673     Reset(FALSE, TRUE);
8674     if (*appData.loadGameFile != NULLCHAR) {
8675         index = appData.loadGameIndex;
8676         if(index < 0) { // [HGM] autoinc
8677             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8678             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8679         } 
8680         LoadGameFromFile(appData.loadGameFile,
8681                          index,
8682                          appData.loadGameFile, FALSE);
8683     } else if (*appData.loadPositionFile != NULLCHAR) {
8684         index = appData.loadPositionIndex;
8685         if(index < 0) { // [HGM] autoinc
8686             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8687             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8688         } 
8689         LoadPositionFromFile(appData.loadPositionFile,
8690                              index,
8691                              appData.loadPositionFile);
8692     }
8693     TwoMachinesEventIfReady();
8694 }
8695
8696 void UserAdjudicationEvent( int result )
8697 {
8698     ChessMove gameResult = GameIsDrawn;
8699
8700     if( result > 0 ) {
8701         gameResult = WhiteWins;
8702     }
8703     else if( result < 0 ) {
8704         gameResult = BlackWins;
8705     }
8706
8707     if( gameMode == TwoMachinesPlay ) {
8708         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8709     }
8710 }
8711
8712
8713 // [HGM] save: calculate checksum of game to make games easily identifiable
8714 int StringCheckSum(char *s)
8715 {
8716         int i = 0;
8717         if(s==NULL) return 0;
8718         while(*s) i = i*259 + *s++;
8719         return i;
8720 }
8721
8722 int GameCheckSum()
8723 {
8724         int i, sum=0;
8725         for(i=backwardMostMove; i<forwardMostMove; i++) {
8726                 sum += pvInfoList[i].depth;
8727                 sum += StringCheckSum(parseList[i]);
8728                 sum += StringCheckSum(commentList[i]);
8729                 sum *= 261;
8730         }
8731         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8732         return sum + StringCheckSum(commentList[i]);
8733 } // end of save patch
8734
8735 void
8736 GameEnds(result, resultDetails, whosays)
8737      ChessMove result;
8738      char *resultDetails;
8739      int whosays;
8740 {
8741     GameMode nextGameMode;
8742     int isIcsGame;
8743     char buf[MSG_SIZ];
8744
8745     if(endingGame) return; /* [HGM] crash: forbid recursion */
8746     endingGame = 1;
8747
8748     if (appData.debugMode) {
8749       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8750               result, resultDetails ? resultDetails : "(null)", whosays);
8751     }
8752
8753     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8754
8755     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8756         /* If we are playing on ICS, the server decides when the
8757            game is over, but the engine can offer to draw, claim 
8758            a draw, or resign. 
8759          */
8760 #if ZIPPY
8761         if (appData.zippyPlay && first.initDone) {
8762             if (result == GameIsDrawn) {
8763                 /* In case draw still needs to be claimed */
8764                 SendToICS(ics_prefix);
8765                 SendToICS("draw\n");
8766             } else if (StrCaseStr(resultDetails, "resign")) {
8767                 SendToICS(ics_prefix);
8768                 SendToICS("resign\n");
8769             }
8770         }
8771 #endif
8772         endingGame = 0; /* [HGM] crash */
8773         return;
8774     }
8775
8776     /* If we're loading the game from a file, stop */
8777     if (whosays == GE_FILE) {
8778       (void) StopLoadGameTimer();
8779       gameFileFP = NULL;
8780     }
8781
8782     /* Cancel draw offers */
8783     first.offeredDraw = second.offeredDraw = 0;
8784
8785     /* If this is an ICS game, only ICS can really say it's done;
8786        if not, anyone can. */
8787     isIcsGame = (gameMode == IcsPlayingWhite || 
8788                  gameMode == IcsPlayingBlack || 
8789                  gameMode == IcsObserving    || 
8790                  gameMode == IcsExamining);
8791
8792     if (!isIcsGame || whosays == GE_ICS) {
8793         /* OK -- not an ICS game, or ICS said it was done */
8794         StopClocks();
8795         if (!isIcsGame && !appData.noChessProgram) 
8796           SetUserThinkingEnables();
8797     
8798         /* [HGM] if a machine claims the game end we verify this claim */
8799         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8800             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8801                 char claimer;
8802                 ChessMove trueResult = (ChessMove) -1;
8803
8804                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8805                                             first.twoMachinesColor[0] :
8806                                             second.twoMachinesColor[0] ;
8807
8808                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8809                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8810                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8811                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8812                 } else
8813                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8814                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8815                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8816                 } else
8817                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8818                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8819                 }
8820
8821                 // now verify win claims, but not in drop games, as we don't understand those yet
8822                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8823                                                  || gameInfo.variant == VariantGreat) &&
8824                     (result == WhiteWins && claimer == 'w' ||
8825                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8826                       if (appData.debugMode) {
8827                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8828                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8829                       }
8830                       if(result != trueResult) {
8831                               sprintf(buf, "False win claim: '%s'", resultDetails);
8832                               result = claimer == 'w' ? BlackWins : WhiteWins;
8833                               resultDetails = buf;
8834                       }
8835                 } else
8836                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8837                     && (forwardMostMove <= backwardMostMove ||
8838                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8839                         (claimer=='b')==(forwardMostMove&1))
8840                                                                                   ) {
8841                       /* [HGM] verify: draws that were not flagged are false claims */
8842                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8843                       result = claimer == 'w' ? BlackWins : WhiteWins;
8844                       resultDetails = buf;
8845                 }
8846                 /* (Claiming a loss is accepted no questions asked!) */
8847             }
8848             /* [HGM] bare: don't allow bare King to win */
8849             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8850                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8851                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8852                && result != GameIsDrawn)
8853             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8854                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8855                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8856                         if(p >= 0 && p <= (int)WhiteKing) k++;
8857                 }
8858                 if (appData.debugMode) {
8859                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8860                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8861                 }
8862                 if(k <= 1) {
8863                         result = GameIsDrawn;
8864                         sprintf(buf, "%s but bare king", resultDetails);
8865                         resultDetails = buf;
8866                 }
8867             }
8868         }
8869
8870
8871         if(serverMoves != NULL && !loadFlag) { char c = '=';
8872             if(result==WhiteWins) c = '+';
8873             if(result==BlackWins) c = '-';
8874             if(resultDetails != NULL)
8875                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8876         }
8877         if (resultDetails != NULL) {
8878             gameInfo.result = result;
8879             gameInfo.resultDetails = StrSave(resultDetails);
8880
8881             /* display last move only if game was not loaded from file */
8882             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8883                 DisplayMove(currentMove - 1);
8884     
8885             if (forwardMostMove != 0) {
8886                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8887                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8888                                                                 ) {
8889                     if (*appData.saveGameFile != NULLCHAR) {
8890                         SaveGameToFile(appData.saveGameFile, TRUE);
8891                     } else if (appData.autoSaveGames) {
8892                         AutoSaveGame();
8893                     }
8894                     if (*appData.savePositionFile != NULLCHAR) {
8895                         SavePositionToFile(appData.savePositionFile);
8896                     }
8897                 }
8898             }
8899
8900             /* Tell program how game ended in case it is learning */
8901             /* [HGM] Moved this to after saving the PGN, just in case */
8902             /* engine died and we got here through time loss. In that */
8903             /* case we will get a fatal error writing the pipe, which */
8904             /* would otherwise lose us the PGN.                       */
8905             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8906             /* output during GameEnds should never be fatal anymore   */
8907             if (gameMode == MachinePlaysWhite ||
8908                 gameMode == MachinePlaysBlack ||
8909                 gameMode == TwoMachinesPlay ||
8910                 gameMode == IcsPlayingWhite ||
8911                 gameMode == IcsPlayingBlack ||
8912                 gameMode == BeginningOfGame) {
8913                 char buf[MSG_SIZ];
8914                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8915                         resultDetails);
8916                 if (first.pr != NoProc) {
8917                     SendToProgram(buf, &first);
8918                 }
8919                 if (second.pr != NoProc &&
8920                     gameMode == TwoMachinesPlay) {
8921                     SendToProgram(buf, &second);
8922                 }
8923             }
8924         }
8925
8926         if (appData.icsActive) {
8927             if (appData.quietPlay &&
8928                 (gameMode == IcsPlayingWhite ||
8929                  gameMode == IcsPlayingBlack)) {
8930                 SendToICS(ics_prefix);
8931                 SendToICS("set shout 1\n");
8932             }
8933             nextGameMode = IcsIdle;
8934             ics_user_moved = FALSE;
8935             /* clean up premove.  It's ugly when the game has ended and the
8936              * premove highlights are still on the board.
8937              */
8938             if (gotPremove) {
8939               gotPremove = FALSE;
8940               ClearPremoveHighlights();
8941               DrawPosition(FALSE, boards[currentMove]);
8942             }
8943             if (whosays == GE_ICS) {
8944                 switch (result) {
8945                 case WhiteWins:
8946                     if (gameMode == IcsPlayingWhite)
8947                         PlayIcsWinSound();
8948                     else if(gameMode == IcsPlayingBlack)
8949                         PlayIcsLossSound();
8950                     break;
8951                 case BlackWins:
8952                     if (gameMode == IcsPlayingBlack)
8953                         PlayIcsWinSound();
8954                     else if(gameMode == IcsPlayingWhite)
8955                         PlayIcsLossSound();
8956                     break;
8957                 case GameIsDrawn:
8958                     PlayIcsDrawSound();
8959                     break;
8960                 default:
8961                     PlayIcsUnfinishedSound();
8962                 }
8963             }
8964         } else if (gameMode == EditGame ||
8965                    gameMode == PlayFromGameFile || 
8966                    gameMode == AnalyzeMode || 
8967                    gameMode == AnalyzeFile) {
8968             nextGameMode = gameMode;
8969         } else {
8970             nextGameMode = EndOfGame;
8971         }
8972         pausing = FALSE;
8973         ModeHighlight();
8974     } else {
8975         nextGameMode = gameMode;
8976     }
8977
8978     if (appData.noChessProgram) {
8979         gameMode = nextGameMode;
8980         ModeHighlight();
8981         endingGame = 0; /* [HGM] crash */
8982         return;
8983     }
8984
8985     if (first.reuse) {
8986         /* Put first chess program into idle state */
8987         if (first.pr != NoProc &&
8988             (gameMode == MachinePlaysWhite ||
8989              gameMode == MachinePlaysBlack ||
8990              gameMode == TwoMachinesPlay ||
8991              gameMode == IcsPlayingWhite ||
8992              gameMode == IcsPlayingBlack ||
8993              gameMode == BeginningOfGame)) {
8994             SendToProgram("force\n", &first);
8995             if (first.usePing) {
8996               char buf[MSG_SIZ];
8997               sprintf(buf, "ping %d\n", ++first.lastPing);
8998               SendToProgram(buf, &first);
8999             }
9000         }
9001     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9002         /* Kill off first chess program */
9003         if (first.isr != NULL)
9004           RemoveInputSource(first.isr);
9005         first.isr = NULL;
9006     
9007         if (first.pr != NoProc) {
9008             ExitAnalyzeMode();
9009             DoSleep( appData.delayBeforeQuit );
9010             SendToProgram("quit\n", &first);
9011             DoSleep( appData.delayAfterQuit );
9012             DestroyChildProcess(first.pr, first.useSigterm);
9013         }
9014         first.pr = NoProc;
9015     }
9016     if (second.reuse) {
9017         /* Put second chess program into idle state */
9018         if (second.pr != NoProc &&
9019             gameMode == TwoMachinesPlay) {
9020             SendToProgram("force\n", &second);
9021             if (second.usePing) {
9022               char buf[MSG_SIZ];
9023               sprintf(buf, "ping %d\n", ++second.lastPing);
9024               SendToProgram(buf, &second);
9025             }
9026         }
9027     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9028         /* Kill off second chess program */
9029         if (second.isr != NULL)
9030           RemoveInputSource(second.isr);
9031         second.isr = NULL;
9032     
9033         if (second.pr != NoProc) {
9034             DoSleep( appData.delayBeforeQuit );
9035             SendToProgram("quit\n", &second);
9036             DoSleep( appData.delayAfterQuit );
9037             DestroyChildProcess(second.pr, second.useSigterm);
9038         }
9039         second.pr = NoProc;
9040     }
9041
9042     if (matchMode && gameMode == TwoMachinesPlay) {
9043         switch (result) {
9044         case WhiteWins:
9045           if (first.twoMachinesColor[0] == 'w') {
9046             first.matchWins++;
9047           } else {
9048             second.matchWins++;
9049           }
9050           break;
9051         case BlackWins:
9052           if (first.twoMachinesColor[0] == 'b') {
9053             first.matchWins++;
9054           } else {
9055             second.matchWins++;
9056           }
9057           break;
9058         default:
9059           break;
9060         }
9061         if (matchGame < appData.matchGames) {
9062             char *tmp;
9063             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9064                 tmp = first.twoMachinesColor;
9065                 first.twoMachinesColor = second.twoMachinesColor;
9066                 second.twoMachinesColor = tmp;
9067             }
9068             gameMode = nextGameMode;
9069             matchGame++;
9070             if(appData.matchPause>10000 || appData.matchPause<10)
9071                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9072             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9073             endingGame = 0; /* [HGM] crash */
9074             return;
9075         } else {
9076             char buf[MSG_SIZ];
9077             gameMode = nextGameMode;
9078             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9079                     first.tidy, second.tidy,
9080                     first.matchWins, second.matchWins,
9081                     appData.matchGames - (first.matchWins + second.matchWins));
9082             DisplayFatalError(buf, 0, 0);
9083         }
9084     }
9085     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9086         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9087       ExitAnalyzeMode();
9088     gameMode = nextGameMode;
9089     ModeHighlight();
9090     endingGame = 0;  /* [HGM] crash */
9091 }
9092
9093 /* Assumes program was just initialized (initString sent).
9094    Leaves program in force mode. */
9095 void
9096 FeedMovesToProgram(cps, upto) 
9097      ChessProgramState *cps;
9098      int upto;
9099 {
9100     int i;
9101     
9102     if (appData.debugMode)
9103       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9104               startedFromSetupPosition ? "position and " : "",
9105               backwardMostMove, upto, cps->which);
9106     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9107         // [HGM] variantswitch: make engine aware of new variant
9108         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9109                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9110         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9111         SendToProgram(buf, cps);
9112         currentlyInitializedVariant = gameInfo.variant;
9113     }
9114     SendToProgram("force\n", cps);
9115     if (startedFromSetupPosition) {
9116         SendBoard(cps, backwardMostMove);
9117     if (appData.debugMode) {
9118         fprintf(debugFP, "feedMoves\n");
9119     }
9120     }
9121     for (i = backwardMostMove; i < upto; i++) {
9122         SendMoveToProgram(i, cps);
9123     }
9124 }
9125
9126
9127 void
9128 ResurrectChessProgram()
9129 {
9130      /* The chess program may have exited.
9131         If so, restart it and feed it all the moves made so far. */
9132
9133     if (appData.noChessProgram || first.pr != NoProc) return;
9134     
9135     StartChessProgram(&first);
9136     InitChessProgram(&first, FALSE);
9137     FeedMovesToProgram(&first, currentMove);
9138
9139     if (!first.sendTime) {
9140         /* can't tell gnuchess what its clock should read,
9141            so we bow to its notion. */
9142         ResetClocks();
9143         timeRemaining[0][currentMove] = whiteTimeRemaining;
9144         timeRemaining[1][currentMove] = blackTimeRemaining;
9145     }
9146
9147     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9148                 appData.icsEngineAnalyze) && first.analysisSupport) {
9149       SendToProgram("analyze\n", &first);
9150       first.analyzing = TRUE;
9151     }
9152 }
9153
9154 /*
9155  * Button procedures
9156  */
9157 void
9158 Reset(redraw, init)
9159      int redraw, init;
9160 {
9161     int i;
9162
9163     if (appData.debugMode) {
9164         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9165                 redraw, init, gameMode);
9166     }
9167     CleanupTail(); // [HGM] vari: delete any stored variations
9168     pausing = pauseExamInvalid = FALSE;
9169     startedFromSetupPosition = blackPlaysFirst = FALSE;
9170     firstMove = TRUE;
9171     whiteFlag = blackFlag = FALSE;
9172     userOfferedDraw = FALSE;
9173     hintRequested = bookRequested = FALSE;
9174     first.maybeThinking = FALSE;
9175     second.maybeThinking = FALSE;
9176     first.bookSuspend = FALSE; // [HGM] book
9177     second.bookSuspend = FALSE;
9178     thinkOutput[0] = NULLCHAR;
9179     lastHint[0] = NULLCHAR;
9180     ClearGameInfo(&gameInfo);
9181     gameInfo.variant = StringToVariant(appData.variant);
9182     ics_user_moved = ics_clock_paused = FALSE;
9183     ics_getting_history = H_FALSE;
9184     ics_gamenum = -1;
9185     white_holding[0] = black_holding[0] = NULLCHAR;
9186     ClearProgramStats();
9187     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9188     
9189     ResetFrontEnd();
9190     ClearHighlights();
9191     flipView = appData.flipView;
9192     ClearPremoveHighlights();
9193     gotPremove = FALSE;
9194     alarmSounded = FALSE;
9195
9196     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9197     if(appData.serverMovesName != NULL) {
9198         /* [HGM] prepare to make moves file for broadcasting */
9199         clock_t t = clock();
9200         if(serverMoves != NULL) fclose(serverMoves);
9201         serverMoves = fopen(appData.serverMovesName, "r");
9202         if(serverMoves != NULL) {
9203             fclose(serverMoves);
9204             /* delay 15 sec before overwriting, so all clients can see end */
9205             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9206         }
9207         serverMoves = fopen(appData.serverMovesName, "w");
9208     }
9209
9210     ExitAnalyzeMode();
9211     gameMode = BeginningOfGame;
9212     ModeHighlight();
9213     if(appData.icsActive) gameInfo.variant = VariantNormal;
9214     currentMove = forwardMostMove = backwardMostMove = 0;
9215     InitPosition(redraw);
9216     for (i = 0; i < MAX_MOVES; i++) {
9217         if (commentList[i] != NULL) {
9218             free(commentList[i]);
9219             commentList[i] = NULL;
9220         }
9221     }
9222     ResetClocks();
9223     timeRemaining[0][0] = whiteTimeRemaining;
9224     timeRemaining[1][0] = blackTimeRemaining;
9225     if (first.pr == NULL) {
9226         StartChessProgram(&first);
9227     }
9228     if (init) {
9229             InitChessProgram(&first, startedFromSetupPosition);
9230     }
9231     DisplayTitle("");
9232     DisplayMessage("", "");
9233     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9234     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9235 }
9236
9237 void
9238 AutoPlayGameLoop()
9239 {
9240     for (;;) {
9241         if (!AutoPlayOneMove())
9242           return;
9243         if (matchMode || appData.timeDelay == 0)
9244           continue;
9245         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9246           return;
9247         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9248         break;
9249     }
9250 }
9251
9252
9253 int
9254 AutoPlayOneMove()
9255 {
9256     int fromX, fromY, toX, toY;
9257
9258     if (appData.debugMode) {
9259       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9260     }
9261
9262     if (gameMode != PlayFromGameFile)
9263       return FALSE;
9264
9265     if (currentMove >= forwardMostMove) {
9266       gameMode = EditGame;
9267       ModeHighlight();
9268
9269       /* [AS] Clear current move marker at the end of a game */
9270       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9271
9272       return FALSE;
9273     }
9274     
9275     toX = moveList[currentMove][2] - AAA;
9276     toY = moveList[currentMove][3] - ONE;
9277
9278     if (moveList[currentMove][1] == '@') {
9279         if (appData.highlightLastMove) {
9280             SetHighlights(-1, -1, toX, toY);
9281         }
9282     } else {
9283         fromX = moveList[currentMove][0] - AAA;
9284         fromY = moveList[currentMove][1] - ONE;
9285
9286         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9287
9288         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9289
9290         if (appData.highlightLastMove) {
9291             SetHighlights(fromX, fromY, toX, toY);
9292         }
9293     }
9294     DisplayMove(currentMove);
9295     SendMoveToProgram(currentMove++, &first);
9296     DisplayBothClocks();
9297     DrawPosition(FALSE, boards[currentMove]);
9298     // [HGM] PV info: always display, routine tests if empty
9299     DisplayComment(currentMove - 1, commentList[currentMove]);
9300     return TRUE;
9301 }
9302
9303
9304 int
9305 LoadGameOneMove(readAhead)
9306      ChessMove readAhead;
9307 {
9308     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9309     char promoChar = NULLCHAR;
9310     ChessMove moveType;
9311     char move[MSG_SIZ];
9312     char *p, *q;
9313     
9314     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9315         gameMode != AnalyzeMode && gameMode != Training) {
9316         gameFileFP = NULL;
9317         return FALSE;
9318     }
9319     
9320     yyboardindex = forwardMostMove;
9321     if (readAhead != (ChessMove)0) {
9322       moveType = readAhead;
9323     } else {
9324       if (gameFileFP == NULL)
9325           return FALSE;
9326       moveType = (ChessMove) yylex();
9327     }
9328     
9329     done = FALSE;
9330     switch (moveType) {
9331       case Comment:
9332         if (appData.debugMode) 
9333           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9334         p = yy_text;
9335
9336         /* append the comment but don't display it */
9337         AppendComment(currentMove, p, FALSE);
9338         return TRUE;
9339
9340       case WhiteCapturesEnPassant:
9341       case BlackCapturesEnPassant:
9342       case WhitePromotionChancellor:
9343       case BlackPromotionChancellor:
9344       case WhitePromotionArchbishop:
9345       case BlackPromotionArchbishop:
9346       case WhitePromotionCentaur:
9347       case BlackPromotionCentaur:
9348       case WhitePromotionQueen:
9349       case BlackPromotionQueen:
9350       case WhitePromotionRook:
9351       case BlackPromotionRook:
9352       case WhitePromotionBishop:
9353       case BlackPromotionBishop:
9354       case WhitePromotionKnight:
9355       case BlackPromotionKnight:
9356       case WhitePromotionKing:
9357       case BlackPromotionKing:
9358       case NormalMove:
9359       case WhiteKingSideCastle:
9360       case WhiteQueenSideCastle:
9361       case BlackKingSideCastle:
9362       case BlackQueenSideCastle:
9363       case WhiteKingSideCastleWild:
9364       case WhiteQueenSideCastleWild:
9365       case BlackKingSideCastleWild:
9366       case BlackQueenSideCastleWild:
9367       /* PUSH Fabien */
9368       case WhiteHSideCastleFR:
9369       case WhiteASideCastleFR:
9370       case BlackHSideCastleFR:
9371       case BlackASideCastleFR:
9372       /* POP Fabien */
9373         if (appData.debugMode)
9374           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9375         fromX = currentMoveString[0] - AAA;
9376         fromY = currentMoveString[1] - ONE;
9377         toX = currentMoveString[2] - AAA;
9378         toY = currentMoveString[3] - ONE;
9379         promoChar = currentMoveString[4];
9380         break;
9381
9382       case WhiteDrop:
9383       case BlackDrop:
9384         if (appData.debugMode)
9385           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9386         fromX = moveType == WhiteDrop ?
9387           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9388         (int) CharToPiece(ToLower(currentMoveString[0]));
9389         fromY = DROP_RANK;
9390         toX = currentMoveString[2] - AAA;
9391         toY = currentMoveString[3] - ONE;
9392         break;
9393
9394       case WhiteWins:
9395       case BlackWins:
9396       case GameIsDrawn:
9397       case GameUnfinished:
9398         if (appData.debugMode)
9399           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9400         p = strchr(yy_text, '{');
9401         if (p == NULL) p = strchr(yy_text, '(');
9402         if (p == NULL) {
9403             p = yy_text;
9404             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9405         } else {
9406             q = strchr(p, *p == '{' ? '}' : ')');
9407             if (q != NULL) *q = NULLCHAR;
9408             p++;
9409         }
9410         GameEnds(moveType, p, GE_FILE);
9411         done = TRUE;
9412         if (cmailMsgLoaded) {
9413             ClearHighlights();
9414             flipView = WhiteOnMove(currentMove);
9415             if (moveType == GameUnfinished) flipView = !flipView;
9416             if (appData.debugMode)
9417               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9418         }
9419         break;
9420
9421       case (ChessMove) 0:       /* end of file */
9422         if (appData.debugMode)
9423           fprintf(debugFP, "Parser hit end of file\n");
9424         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9425           case MT_NONE:
9426           case MT_CHECK:
9427             break;
9428           case MT_CHECKMATE:
9429           case MT_STAINMATE:
9430             if (WhiteOnMove(currentMove)) {
9431                 GameEnds(BlackWins, "Black mates", GE_FILE);
9432             } else {
9433                 GameEnds(WhiteWins, "White mates", GE_FILE);
9434             }
9435             break;
9436           case MT_STALEMATE:
9437             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9438             break;
9439         }
9440         done = TRUE;
9441         break;
9442
9443       case MoveNumberOne:
9444         if (lastLoadGameStart == GNUChessGame) {
9445             /* GNUChessGames have numbers, but they aren't move numbers */
9446             if (appData.debugMode)
9447               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9448                       yy_text, (int) moveType);
9449             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9450         }
9451         /* else fall thru */
9452
9453       case XBoardGame:
9454       case GNUChessGame:
9455       case PGNTag:
9456         /* Reached start of next game in file */
9457         if (appData.debugMode)
9458           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9459         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9460           case MT_NONE:
9461           case MT_CHECK:
9462             break;
9463           case MT_CHECKMATE:
9464           case MT_STAINMATE:
9465             if (WhiteOnMove(currentMove)) {
9466                 GameEnds(BlackWins, "Black mates", GE_FILE);
9467             } else {
9468                 GameEnds(WhiteWins, "White mates", GE_FILE);
9469             }
9470             break;
9471           case MT_STALEMATE:
9472             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9473             break;
9474         }
9475         done = TRUE;
9476         break;
9477
9478       case PositionDiagram:     /* should not happen; ignore */
9479       case ElapsedTime:         /* ignore */
9480       case NAG:                 /* ignore */
9481         if (appData.debugMode)
9482           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9483                   yy_text, (int) moveType);
9484         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9485
9486       case IllegalMove:
9487         if (appData.testLegality) {
9488             if (appData.debugMode)
9489               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9490             sprintf(move, _("Illegal move: %d.%s%s"),
9491                     (forwardMostMove / 2) + 1,
9492                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9493             DisplayError(move, 0);
9494             done = TRUE;
9495         } else {
9496             if (appData.debugMode)
9497               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9498                       yy_text, currentMoveString);
9499             fromX = currentMoveString[0] - AAA;
9500             fromY = currentMoveString[1] - ONE;
9501             toX = currentMoveString[2] - AAA;
9502             toY = currentMoveString[3] - ONE;
9503             promoChar = currentMoveString[4];
9504         }
9505         break;
9506
9507       case AmbiguousMove:
9508         if (appData.debugMode)
9509           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9510         sprintf(move, _("Ambiguous move: %d.%s%s"),
9511                 (forwardMostMove / 2) + 1,
9512                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9513         DisplayError(move, 0);
9514         done = TRUE;
9515         break;
9516
9517       default:
9518       case ImpossibleMove:
9519         if (appData.debugMode)
9520           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9521         sprintf(move, _("Illegal move: %d.%s%s"),
9522                 (forwardMostMove / 2) + 1,
9523                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9524         DisplayError(move, 0);
9525         done = TRUE;
9526         break;
9527     }
9528
9529     if (done) {
9530         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9531             DrawPosition(FALSE, boards[currentMove]);
9532             DisplayBothClocks();
9533             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9534               DisplayComment(currentMove - 1, commentList[currentMove]);
9535         }
9536         (void) StopLoadGameTimer();
9537         gameFileFP = NULL;
9538         cmailOldMove = forwardMostMove;
9539         return FALSE;
9540     } else {
9541         /* currentMoveString is set as a side-effect of yylex */
9542         strcat(currentMoveString, "\n");
9543         strcpy(moveList[forwardMostMove], currentMoveString);
9544         
9545         thinkOutput[0] = NULLCHAR;
9546         MakeMove(fromX, fromY, toX, toY, promoChar);
9547         currentMove = forwardMostMove;
9548         return TRUE;
9549     }
9550 }
9551
9552 /* Load the nth game from the given file */
9553 int
9554 LoadGameFromFile(filename, n, title, useList)
9555      char *filename;
9556      int n;
9557      char *title;
9558      /*Boolean*/ int useList;
9559 {
9560     FILE *f;
9561     char buf[MSG_SIZ];
9562
9563     if (strcmp(filename, "-") == 0) {
9564         f = stdin;
9565         title = "stdin";
9566     } else {
9567         f = fopen(filename, "rb");
9568         if (f == NULL) {
9569           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9570             DisplayError(buf, errno);
9571             return FALSE;
9572         }
9573     }
9574     if (fseek(f, 0, 0) == -1) {
9575         /* f is not seekable; probably a pipe */
9576         useList = FALSE;
9577     }
9578     if (useList && n == 0) {
9579         int error = GameListBuild(f);
9580         if (error) {
9581             DisplayError(_("Cannot build game list"), error);
9582         } else if (!ListEmpty(&gameList) &&
9583                    ((ListGame *) gameList.tailPred)->number > 1) {
9584             GameListPopUp(f, title);
9585             return TRUE;
9586         }
9587         GameListDestroy();
9588         n = 1;
9589     }
9590     if (n == 0) n = 1;
9591     return LoadGame(f, n, title, FALSE);
9592 }
9593
9594
9595 void
9596 MakeRegisteredMove()
9597 {
9598     int fromX, fromY, toX, toY;
9599     char promoChar;
9600     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9601         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9602           case CMAIL_MOVE:
9603           case CMAIL_DRAW:
9604             if (appData.debugMode)
9605               fprintf(debugFP, "Restoring %s for game %d\n",
9606                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9607     
9608             thinkOutput[0] = NULLCHAR;
9609             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9610             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9611             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9612             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9613             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9614             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9615             MakeMove(fromX, fromY, toX, toY, promoChar);
9616             ShowMove(fromX, fromY, toX, toY);
9617               
9618             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9619               case MT_NONE:
9620               case MT_CHECK:
9621                 break;
9622                 
9623               case MT_CHECKMATE:
9624               case MT_STAINMATE:
9625                 if (WhiteOnMove(currentMove)) {
9626                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9627                 } else {
9628                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9629                 }
9630                 break;
9631                 
9632               case MT_STALEMATE:
9633                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9634                 break;
9635             }
9636
9637             break;
9638             
9639           case CMAIL_RESIGN:
9640             if (WhiteOnMove(currentMove)) {
9641                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9642             } else {
9643                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9644             }
9645             break;
9646             
9647           case CMAIL_ACCEPT:
9648             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9649             break;
9650               
9651           default:
9652             break;
9653         }
9654     }
9655
9656     return;
9657 }
9658
9659 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9660 int
9661 CmailLoadGame(f, gameNumber, title, useList)
9662      FILE *f;
9663      int gameNumber;
9664      char *title;
9665      int useList;
9666 {
9667     int retVal;
9668
9669     if (gameNumber > nCmailGames) {
9670         DisplayError(_("No more games in this message"), 0);
9671         return FALSE;
9672     }
9673     if (f == lastLoadGameFP) {
9674         int offset = gameNumber - lastLoadGameNumber;
9675         if (offset == 0) {
9676             cmailMsg[0] = NULLCHAR;
9677             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9678                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9679                 nCmailMovesRegistered--;
9680             }
9681             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9682             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9683                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9684             }
9685         } else {
9686             if (! RegisterMove()) return FALSE;
9687         }
9688     }
9689
9690     retVal = LoadGame(f, gameNumber, title, useList);
9691
9692     /* Make move registered during previous look at this game, if any */
9693     MakeRegisteredMove();
9694
9695     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9696         commentList[currentMove]
9697           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9698         DisplayComment(currentMove - 1, commentList[currentMove]);
9699     }
9700
9701     return retVal;
9702 }
9703
9704 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9705 int
9706 ReloadGame(offset)
9707      int offset;
9708 {
9709     int gameNumber = lastLoadGameNumber + offset;
9710     if (lastLoadGameFP == NULL) {
9711         DisplayError(_("No game has been loaded yet"), 0);
9712         return FALSE;
9713     }
9714     if (gameNumber <= 0) {
9715         DisplayError(_("Can't back up any further"), 0);
9716         return FALSE;
9717     }
9718     if (cmailMsgLoaded) {
9719         return CmailLoadGame(lastLoadGameFP, gameNumber,
9720                              lastLoadGameTitle, lastLoadGameUseList);
9721     } else {
9722         return LoadGame(lastLoadGameFP, gameNumber,
9723                         lastLoadGameTitle, lastLoadGameUseList);
9724     }
9725 }
9726
9727
9728
9729 /* Load the nth game from open file f */
9730 int
9731 LoadGame(f, gameNumber, title, useList)
9732      FILE *f;
9733      int gameNumber;
9734      char *title;
9735      int useList;
9736 {
9737     ChessMove cm;
9738     char buf[MSG_SIZ];
9739     int gn = gameNumber;
9740     ListGame *lg = NULL;
9741     int numPGNTags = 0;
9742     int err;
9743     GameMode oldGameMode;
9744     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9745
9746     if (appData.debugMode) 
9747         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9748
9749     if (gameMode == Training )
9750         SetTrainingModeOff();
9751
9752     oldGameMode = gameMode;
9753     if (gameMode != BeginningOfGame) {
9754       Reset(FALSE, TRUE);
9755     }
9756
9757     gameFileFP = f;
9758     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9759         fclose(lastLoadGameFP);
9760     }
9761
9762     if (useList) {
9763         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9764         
9765         if (lg) {
9766             fseek(f, lg->offset, 0);
9767             GameListHighlight(gameNumber);
9768             gn = 1;
9769         }
9770         else {
9771             DisplayError(_("Game number out of range"), 0);
9772             return FALSE;
9773         }
9774     } else {
9775         GameListDestroy();
9776         if (fseek(f, 0, 0) == -1) {
9777             if (f == lastLoadGameFP ?
9778                 gameNumber == lastLoadGameNumber + 1 :
9779                 gameNumber == 1) {
9780                 gn = 1;
9781             } else {
9782                 DisplayError(_("Can't seek on game file"), 0);
9783                 return FALSE;
9784             }
9785         }
9786     }
9787     lastLoadGameFP = f;
9788     lastLoadGameNumber = gameNumber;
9789     strcpy(lastLoadGameTitle, title);
9790     lastLoadGameUseList = useList;
9791
9792     yynewfile(f);
9793
9794     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9795       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9796                 lg->gameInfo.black);
9797             DisplayTitle(buf);
9798     } else if (*title != NULLCHAR) {
9799         if (gameNumber > 1) {
9800             sprintf(buf, "%s %d", title, gameNumber);
9801             DisplayTitle(buf);
9802         } else {
9803             DisplayTitle(title);
9804         }
9805     }
9806
9807     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9808         gameMode = PlayFromGameFile;
9809         ModeHighlight();
9810     }
9811
9812     currentMove = forwardMostMove = backwardMostMove = 0;
9813     CopyBoard(boards[0], initialPosition);
9814     StopClocks();
9815
9816     /*
9817      * Skip the first gn-1 games in the file.
9818      * Also skip over anything that precedes an identifiable 
9819      * start of game marker, to avoid being confused by 
9820      * garbage at the start of the file.  Currently 
9821      * recognized start of game markers are the move number "1",
9822      * the pattern "gnuchess .* game", the pattern
9823      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9824      * A game that starts with one of the latter two patterns
9825      * will also have a move number 1, possibly
9826      * following a position diagram.
9827      * 5-4-02: Let's try being more lenient and allowing a game to
9828      * start with an unnumbered move.  Does that break anything?
9829      */
9830     cm = lastLoadGameStart = (ChessMove) 0;
9831     while (gn > 0) {
9832         yyboardindex = forwardMostMove;
9833         cm = (ChessMove) yylex();
9834         switch (cm) {
9835           case (ChessMove) 0:
9836             if (cmailMsgLoaded) {
9837                 nCmailGames = CMAIL_MAX_GAMES - gn;
9838             } else {
9839                 Reset(TRUE, TRUE);
9840                 DisplayError(_("Game not found in file"), 0);
9841             }
9842             return FALSE;
9843
9844           case GNUChessGame:
9845           case XBoardGame:
9846             gn--;
9847             lastLoadGameStart = cm;
9848             break;
9849             
9850           case MoveNumberOne:
9851             switch (lastLoadGameStart) {
9852               case GNUChessGame:
9853               case XBoardGame:
9854               case PGNTag:
9855                 break;
9856               case MoveNumberOne:
9857               case (ChessMove) 0:
9858                 gn--;           /* count this game */
9859                 lastLoadGameStart = cm;
9860                 break;
9861               default:
9862                 /* impossible */
9863                 break;
9864             }
9865             break;
9866
9867           case PGNTag:
9868             switch (lastLoadGameStart) {
9869               case GNUChessGame:
9870               case PGNTag:
9871               case MoveNumberOne:
9872               case (ChessMove) 0:
9873                 gn--;           /* count this game */
9874                 lastLoadGameStart = cm;
9875                 break;
9876               case XBoardGame:
9877                 lastLoadGameStart = cm; /* game counted already */
9878                 break;
9879               default:
9880                 /* impossible */
9881                 break;
9882             }
9883             if (gn > 0) {
9884                 do {
9885                     yyboardindex = forwardMostMove;
9886                     cm = (ChessMove) yylex();
9887                 } while (cm == PGNTag || cm == Comment);
9888             }
9889             break;
9890
9891           case WhiteWins:
9892           case BlackWins:
9893           case GameIsDrawn:
9894             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9895                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9896                     != CMAIL_OLD_RESULT) {
9897                     nCmailResults ++ ;
9898                     cmailResult[  CMAIL_MAX_GAMES
9899                                 - gn - 1] = CMAIL_OLD_RESULT;
9900                 }
9901             }
9902             break;
9903
9904           case NormalMove:
9905             /* Only a NormalMove can be at the start of a game
9906              * without a position diagram. */
9907             if (lastLoadGameStart == (ChessMove) 0) {
9908               gn--;
9909               lastLoadGameStart = MoveNumberOne;
9910             }
9911             break;
9912
9913           default:
9914             break;
9915         }
9916     }
9917     
9918     if (appData.debugMode)
9919       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9920
9921     if (cm == XBoardGame) {
9922         /* Skip any header junk before position diagram and/or move 1 */
9923         for (;;) {
9924             yyboardindex = forwardMostMove;
9925             cm = (ChessMove) yylex();
9926
9927             if (cm == (ChessMove) 0 ||
9928                 cm == GNUChessGame || cm == XBoardGame) {
9929                 /* Empty game; pretend end-of-file and handle later */
9930                 cm = (ChessMove) 0;
9931                 break;
9932             }
9933
9934             if (cm == MoveNumberOne || cm == PositionDiagram ||
9935                 cm == PGNTag || cm == Comment)
9936               break;
9937         }
9938     } else if (cm == GNUChessGame) {
9939         if (gameInfo.event != NULL) {
9940             free(gameInfo.event);
9941         }
9942         gameInfo.event = StrSave(yy_text);
9943     }   
9944
9945     startedFromSetupPosition = FALSE;
9946     while (cm == PGNTag) {
9947         if (appData.debugMode) 
9948           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9949         err = ParsePGNTag(yy_text, &gameInfo);
9950         if (!err) numPGNTags++;
9951
9952         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9953         if(gameInfo.variant != oldVariant) {
9954             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9955             InitPosition(TRUE);
9956             oldVariant = gameInfo.variant;
9957             if (appData.debugMode) 
9958               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9959         }
9960
9961
9962         if (gameInfo.fen != NULL) {
9963           Board initial_position;
9964           startedFromSetupPosition = TRUE;
9965           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9966             Reset(TRUE, TRUE);
9967             DisplayError(_("Bad FEN position in file"), 0);
9968             return FALSE;
9969           }
9970           CopyBoard(boards[0], initial_position);
9971           if (blackPlaysFirst) {
9972             currentMove = forwardMostMove = backwardMostMove = 1;
9973             CopyBoard(boards[1], initial_position);
9974             strcpy(moveList[0], "");
9975             strcpy(parseList[0], "");
9976             timeRemaining[0][1] = whiteTimeRemaining;
9977             timeRemaining[1][1] = blackTimeRemaining;
9978             if (commentList[0] != NULL) {
9979               commentList[1] = commentList[0];
9980               commentList[0] = NULL;
9981             }
9982           } else {
9983             currentMove = forwardMostMove = backwardMostMove = 0;
9984           }
9985           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9986           {   int i;
9987               initialRulePlies = FENrulePlies;
9988               for( i=0; i< nrCastlingRights; i++ )
9989                   initialRights[i] = initial_position[CASTLING][i];
9990           }
9991           yyboardindex = forwardMostMove;
9992           free(gameInfo.fen);
9993           gameInfo.fen = NULL;
9994         }
9995
9996         yyboardindex = forwardMostMove;
9997         cm = (ChessMove) yylex();
9998
9999         /* Handle comments interspersed among the tags */
10000         while (cm == Comment) {
10001             char *p;
10002             if (appData.debugMode) 
10003               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10004             p = yy_text;
10005             AppendComment(currentMove, p, FALSE);
10006             yyboardindex = forwardMostMove;
10007             cm = (ChessMove) yylex();
10008         }
10009     }
10010
10011     /* don't rely on existence of Event tag since if game was
10012      * pasted from clipboard the Event tag may not exist
10013      */
10014     if (numPGNTags > 0){
10015         char *tags;
10016         if (gameInfo.variant == VariantNormal) {
10017           gameInfo.variant = StringToVariant(gameInfo.event);
10018         }
10019         if (!matchMode) {
10020           if( appData.autoDisplayTags ) {
10021             tags = PGNTags(&gameInfo);
10022             TagsPopUp(tags, CmailMsg());
10023             free(tags);
10024           }
10025         }
10026     } else {
10027         /* Make something up, but don't display it now */
10028         SetGameInfo();
10029         TagsPopDown();
10030     }
10031
10032     if (cm == PositionDiagram) {
10033         int i, j;
10034         char *p;
10035         Board initial_position;
10036
10037         if (appData.debugMode)
10038           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10039
10040         if (!startedFromSetupPosition) {
10041             p = yy_text;
10042             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10043               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10044                 switch (*p) {
10045                   case '[':
10046                   case '-':
10047                   case ' ':
10048                   case '\t':
10049                   case '\n':
10050                   case '\r':
10051                     break;
10052                   default:
10053                     initial_position[i][j++] = CharToPiece(*p);
10054                     break;
10055                 }
10056             while (*p == ' ' || *p == '\t' ||
10057                    *p == '\n' || *p == '\r') p++;
10058         
10059             if (strncmp(p, "black", strlen("black"))==0)
10060               blackPlaysFirst = TRUE;
10061             else
10062               blackPlaysFirst = FALSE;
10063             startedFromSetupPosition = TRUE;
10064         
10065             CopyBoard(boards[0], initial_position);
10066             if (blackPlaysFirst) {
10067                 currentMove = forwardMostMove = backwardMostMove = 1;
10068                 CopyBoard(boards[1], initial_position);
10069                 strcpy(moveList[0], "");
10070                 strcpy(parseList[0], "");
10071                 timeRemaining[0][1] = whiteTimeRemaining;
10072                 timeRemaining[1][1] = blackTimeRemaining;
10073                 if (commentList[0] != NULL) {
10074                     commentList[1] = commentList[0];
10075                     commentList[0] = NULL;
10076                 }
10077             } else {
10078                 currentMove = forwardMostMove = backwardMostMove = 0;
10079             }
10080         }
10081         yyboardindex = forwardMostMove;
10082         cm = (ChessMove) yylex();
10083     }
10084
10085     if (first.pr == NoProc) {
10086         StartChessProgram(&first);
10087     }
10088     InitChessProgram(&first, FALSE);
10089     SendToProgram("force\n", &first);
10090     if (startedFromSetupPosition) {
10091         SendBoard(&first, forwardMostMove);
10092     if (appData.debugMode) {
10093         fprintf(debugFP, "Load Game\n");
10094     }
10095         DisplayBothClocks();
10096     }      
10097
10098     /* [HGM] server: flag to write setup moves in broadcast file as one */
10099     loadFlag = appData.suppressLoadMoves;
10100
10101     while (cm == Comment) {
10102         char *p;
10103         if (appData.debugMode) 
10104           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10105         p = yy_text;
10106         AppendComment(currentMove, p, FALSE);
10107         yyboardindex = forwardMostMove;
10108         cm = (ChessMove) yylex();
10109     }
10110
10111     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10112         cm == WhiteWins || cm == BlackWins ||
10113         cm == GameIsDrawn || cm == GameUnfinished) {
10114         DisplayMessage("", _("No moves in game"));
10115         if (cmailMsgLoaded) {
10116             if (appData.debugMode)
10117               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10118             ClearHighlights();
10119             flipView = FALSE;
10120         }
10121         DrawPosition(FALSE, boards[currentMove]);
10122         DisplayBothClocks();
10123         gameMode = EditGame;
10124         ModeHighlight();
10125         gameFileFP = NULL;
10126         cmailOldMove = 0;
10127         return TRUE;
10128     }
10129
10130     // [HGM] PV info: routine tests if comment empty
10131     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10132         DisplayComment(currentMove - 1, commentList[currentMove]);
10133     }
10134     if (!matchMode && appData.timeDelay != 0) 
10135       DrawPosition(FALSE, boards[currentMove]);
10136
10137     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10138       programStats.ok_to_send = 1;
10139     }
10140
10141     /* if the first token after the PGN tags is a move
10142      * and not move number 1, retrieve it from the parser 
10143      */
10144     if (cm != MoveNumberOne)
10145         LoadGameOneMove(cm);
10146
10147     /* load the remaining moves from the file */
10148     while (LoadGameOneMove((ChessMove)0)) {
10149       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10150       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10151     }
10152
10153     /* rewind to the start of the game */
10154     currentMove = backwardMostMove;
10155
10156     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10157
10158     if (oldGameMode == AnalyzeFile ||
10159         oldGameMode == AnalyzeMode) {
10160       AnalyzeFileEvent();
10161     }
10162
10163     if (matchMode || appData.timeDelay == 0) {
10164       ToEndEvent();
10165       gameMode = EditGame;
10166       ModeHighlight();
10167     } else if (appData.timeDelay > 0) {
10168       AutoPlayGameLoop();
10169     }
10170
10171     if (appData.debugMode) 
10172         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10173
10174     loadFlag = 0; /* [HGM] true game starts */
10175     return TRUE;
10176 }
10177
10178 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10179 int
10180 ReloadPosition(offset)
10181      int offset;
10182 {
10183     int positionNumber = lastLoadPositionNumber + offset;
10184     if (lastLoadPositionFP == NULL) {
10185         DisplayError(_("No position has been loaded yet"), 0);
10186         return FALSE;
10187     }
10188     if (positionNumber <= 0) {
10189         DisplayError(_("Can't back up any further"), 0);
10190         return FALSE;
10191     }
10192     return LoadPosition(lastLoadPositionFP, positionNumber,
10193                         lastLoadPositionTitle);
10194 }
10195
10196 /* Load the nth position from the given file */
10197 int
10198 LoadPositionFromFile(filename, n, title)
10199      char *filename;
10200      int n;
10201      char *title;
10202 {
10203     FILE *f;
10204     char buf[MSG_SIZ];
10205
10206     if (strcmp(filename, "-") == 0) {
10207         return LoadPosition(stdin, n, "stdin");
10208     } else {
10209         f = fopen(filename, "rb");
10210         if (f == NULL) {
10211             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10212             DisplayError(buf, errno);
10213             return FALSE;
10214         } else {
10215             return LoadPosition(f, n, title);
10216         }
10217     }
10218 }
10219
10220 /* Load the nth position from the given open file, and close it */
10221 int
10222 LoadPosition(f, positionNumber, title)
10223      FILE *f;
10224      int positionNumber;
10225      char *title;
10226 {
10227     char *p, line[MSG_SIZ];
10228     Board initial_position;
10229     int i, j, fenMode, pn;
10230     
10231     if (gameMode == Training )
10232         SetTrainingModeOff();
10233
10234     if (gameMode != BeginningOfGame) {
10235         Reset(FALSE, TRUE);
10236     }
10237     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10238         fclose(lastLoadPositionFP);
10239     }
10240     if (positionNumber == 0) positionNumber = 1;
10241     lastLoadPositionFP = f;
10242     lastLoadPositionNumber = positionNumber;
10243     strcpy(lastLoadPositionTitle, title);
10244     if (first.pr == NoProc) {
10245       StartChessProgram(&first);
10246       InitChessProgram(&first, FALSE);
10247     }    
10248     pn = positionNumber;
10249     if (positionNumber < 0) {
10250         /* Negative position number means to seek to that byte offset */
10251         if (fseek(f, -positionNumber, 0) == -1) {
10252             DisplayError(_("Can't seek on position file"), 0);
10253             return FALSE;
10254         };
10255         pn = 1;
10256     } else {
10257         if (fseek(f, 0, 0) == -1) {
10258             if (f == lastLoadPositionFP ?
10259                 positionNumber == lastLoadPositionNumber + 1 :
10260                 positionNumber == 1) {
10261                 pn = 1;
10262             } else {
10263                 DisplayError(_("Can't seek on position file"), 0);
10264                 return FALSE;
10265             }
10266         }
10267     }
10268     /* See if this file is FEN or old-style xboard */
10269     if (fgets(line, MSG_SIZ, f) == NULL) {
10270         DisplayError(_("Position not found in file"), 0);
10271         return FALSE;
10272     }
10273     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10274     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10275
10276     if (pn >= 2) {
10277         if (fenMode || line[0] == '#') pn--;
10278         while (pn > 0) {
10279             /* skip positions before number pn */
10280             if (fgets(line, MSG_SIZ, f) == NULL) {
10281                 Reset(TRUE, TRUE);
10282                 DisplayError(_("Position not found in file"), 0);
10283                 return FALSE;
10284             }
10285             if (fenMode || line[0] == '#') pn--;
10286         }
10287     }
10288
10289     if (fenMode) {
10290         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10291             DisplayError(_("Bad FEN position in file"), 0);
10292             return FALSE;
10293         }
10294     } else {
10295         (void) fgets(line, MSG_SIZ, f);
10296         (void) fgets(line, MSG_SIZ, f);
10297     
10298         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10299             (void) fgets(line, MSG_SIZ, f);
10300             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10301                 if (*p == ' ')
10302                   continue;
10303                 initial_position[i][j++] = CharToPiece(*p);
10304             }
10305         }
10306     
10307         blackPlaysFirst = FALSE;
10308         if (!feof(f)) {
10309             (void) fgets(line, MSG_SIZ, f);
10310             if (strncmp(line, "black", strlen("black"))==0)
10311               blackPlaysFirst = TRUE;
10312         }
10313     }
10314     startedFromSetupPosition = TRUE;
10315     
10316     SendToProgram("force\n", &first);
10317     CopyBoard(boards[0], initial_position);
10318     if (blackPlaysFirst) {
10319         currentMove = forwardMostMove = backwardMostMove = 1;
10320         strcpy(moveList[0], "");
10321         strcpy(parseList[0], "");
10322         CopyBoard(boards[1], initial_position);
10323         DisplayMessage("", _("Black to play"));
10324     } else {
10325         currentMove = forwardMostMove = backwardMostMove = 0;
10326         DisplayMessage("", _("White to play"));
10327     }
10328     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10329     SendBoard(&first, forwardMostMove);
10330     if (appData.debugMode) {
10331 int i, j;
10332   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10333   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10334         fprintf(debugFP, "Load Position\n");
10335     }
10336
10337     if (positionNumber > 1) {
10338         sprintf(line, "%s %d", title, positionNumber);
10339         DisplayTitle(line);
10340     } else {
10341         DisplayTitle(title);
10342     }
10343     gameMode = EditGame;
10344     ModeHighlight();
10345     ResetClocks();
10346     timeRemaining[0][1] = whiteTimeRemaining;
10347     timeRemaining[1][1] = blackTimeRemaining;
10348     DrawPosition(FALSE, boards[currentMove]);
10349    
10350     return TRUE;
10351 }
10352
10353
10354 void
10355 CopyPlayerNameIntoFileName(dest, src)
10356      char **dest, *src;
10357 {
10358     while (*src != NULLCHAR && *src != ',') {
10359         if (*src == ' ') {
10360             *(*dest)++ = '_';
10361             src++;
10362         } else {
10363             *(*dest)++ = *src++;
10364         }
10365     }
10366 }
10367
10368 char *DefaultFileName(ext)
10369      char *ext;
10370 {
10371     static char def[MSG_SIZ];
10372     char *p;
10373
10374     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10375         p = def;
10376         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10377         *p++ = '-';
10378         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10379         *p++ = '.';
10380         strcpy(p, ext);
10381     } else {
10382         def[0] = NULLCHAR;
10383     }
10384     return def;
10385 }
10386
10387 /* Save the current game to the given file */
10388 int
10389 SaveGameToFile(filename, append)
10390      char *filename;
10391      int append;
10392 {
10393     FILE *f;
10394     char buf[MSG_SIZ];
10395
10396     if (strcmp(filename, "-") == 0) {
10397         return SaveGame(stdout, 0, NULL);
10398     } else {
10399         f = fopen(filename, append ? "a" : "w");
10400         if (f == NULL) {
10401             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10402             DisplayError(buf, errno);
10403             return FALSE;
10404         } else {
10405             return SaveGame(f, 0, NULL);
10406         }
10407     }
10408 }
10409
10410 char *
10411 SavePart(str)
10412      char *str;
10413 {
10414     static char buf[MSG_SIZ];
10415     char *p;
10416     
10417     p = strchr(str, ' ');
10418     if (p == NULL) return str;
10419     strncpy(buf, str, p - str);
10420     buf[p - str] = NULLCHAR;
10421     return buf;
10422 }
10423
10424 #define PGN_MAX_LINE 75
10425
10426 #define PGN_SIDE_WHITE  0
10427 #define PGN_SIDE_BLACK  1
10428
10429 /* [AS] */
10430 static int FindFirstMoveOutOfBook( int side )
10431 {
10432     int result = -1;
10433
10434     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10435         int index = backwardMostMove;
10436         int has_book_hit = 0;
10437
10438         if( (index % 2) != side ) {
10439             index++;
10440         }
10441
10442         while( index < forwardMostMove ) {
10443             /* Check to see if engine is in book */
10444             int depth = pvInfoList[index].depth;
10445             int score = pvInfoList[index].score;
10446             int in_book = 0;
10447
10448             if( depth <= 2 ) {
10449                 in_book = 1;
10450             }
10451             else if( score == 0 && depth == 63 ) {
10452                 in_book = 1; /* Zappa */
10453             }
10454             else if( score == 2 && depth == 99 ) {
10455                 in_book = 1; /* Abrok */
10456             }
10457
10458             has_book_hit += in_book;
10459
10460             if( ! in_book ) {
10461                 result = index;
10462
10463                 break;
10464             }
10465
10466             index += 2;
10467         }
10468     }
10469
10470     return result;
10471 }
10472
10473 /* [AS] */
10474 void GetOutOfBookInfo( char * buf )
10475 {
10476     int oob[2];
10477     int i;
10478     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10479
10480     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10481     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10482
10483     *buf = '\0';
10484
10485     if( oob[0] >= 0 || oob[1] >= 0 ) {
10486         for( i=0; i<2; i++ ) {
10487             int idx = oob[i];
10488
10489             if( idx >= 0 ) {
10490                 if( i > 0 && oob[0] >= 0 ) {
10491                     strcat( buf, "   " );
10492                 }
10493
10494                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10495                 sprintf( buf+strlen(buf), "%s%.2f", 
10496                     pvInfoList[idx].score >= 0 ? "+" : "",
10497                     pvInfoList[idx].score / 100.0 );
10498             }
10499         }
10500     }
10501 }
10502
10503 /* Save game in PGN style and close the file */
10504 int
10505 SaveGamePGN(f)
10506      FILE *f;
10507 {
10508     int i, offset, linelen, newblock;
10509     time_t tm;
10510 //    char *movetext;
10511     char numtext[32];
10512     int movelen, numlen, blank;
10513     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10514
10515     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10516     
10517     tm = time((time_t *) NULL);
10518     
10519     PrintPGNTags(f, &gameInfo);
10520     
10521     if (backwardMostMove > 0 || startedFromSetupPosition) {
10522         char *fen = PositionToFEN(backwardMostMove, NULL);
10523         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10524         fprintf(f, "\n{--------------\n");
10525         PrintPosition(f, backwardMostMove);
10526         fprintf(f, "--------------}\n");
10527         free(fen);
10528     }
10529     else {
10530         /* [AS] Out of book annotation */
10531         if( appData.saveOutOfBookInfo ) {
10532             char buf[64];
10533
10534             GetOutOfBookInfo( buf );
10535
10536             if( buf[0] != '\0' ) {
10537                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10538             }
10539         }
10540
10541         fprintf(f, "\n");
10542     }
10543
10544     i = backwardMostMove;
10545     linelen = 0;
10546     newblock = TRUE;
10547
10548     while (i < forwardMostMove) {
10549         /* Print comments preceding this move */
10550         if (commentList[i] != NULL) {
10551             if (linelen > 0) fprintf(f, "\n");
10552             fprintf(f, "%s", commentList[i]);
10553             linelen = 0;
10554             newblock = TRUE;
10555         }
10556
10557         /* Format move number */
10558         if ((i % 2) == 0) {
10559             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10560         } else {
10561             if (newblock) {
10562                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10563             } else {
10564                 numtext[0] = NULLCHAR;
10565             }
10566         }
10567         numlen = strlen(numtext);
10568         newblock = FALSE;
10569
10570         /* Print move number */
10571         blank = linelen > 0 && numlen > 0;
10572         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10573             fprintf(f, "\n");
10574             linelen = 0;
10575             blank = 0;
10576         }
10577         if (blank) {
10578             fprintf(f, " ");
10579             linelen++;
10580         }
10581         fprintf(f, "%s", numtext);
10582         linelen += numlen;
10583
10584         /* Get move */
10585         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10586         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10587
10588         /* Print move */
10589         blank = linelen > 0 && movelen > 0;
10590         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10591             fprintf(f, "\n");
10592             linelen = 0;
10593             blank = 0;
10594         }
10595         if (blank) {
10596             fprintf(f, " ");
10597             linelen++;
10598         }
10599         fprintf(f, "%s", move_buffer);
10600         linelen += movelen;
10601
10602         /* [AS] Add PV info if present */
10603         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10604             /* [HGM] add time */
10605             char buf[MSG_SIZ]; int seconds;
10606
10607             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10608
10609             if( seconds <= 0) buf[0] = 0; else
10610             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10611                 seconds = (seconds + 4)/10; // round to full seconds
10612                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10613                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10614             }
10615
10616             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10617                 pvInfoList[i].score >= 0 ? "+" : "",
10618                 pvInfoList[i].score / 100.0,
10619                 pvInfoList[i].depth,
10620                 buf );
10621
10622             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10623
10624             /* Print score/depth */
10625             blank = linelen > 0 && movelen > 0;
10626             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10627                 fprintf(f, "\n");
10628                 linelen = 0;
10629                 blank = 0;
10630             }
10631             if (blank) {
10632                 fprintf(f, " ");
10633                 linelen++;
10634             }
10635             fprintf(f, "%s", move_buffer);
10636             linelen += movelen;
10637         }
10638
10639         i++;
10640     }
10641     
10642     /* Start a new line */
10643     if (linelen > 0) fprintf(f, "\n");
10644
10645     /* Print comments after last move */
10646     if (commentList[i] != NULL) {
10647         fprintf(f, "%s\n", commentList[i]);
10648     }
10649
10650     /* Print result */
10651     if (gameInfo.resultDetails != NULL &&
10652         gameInfo.resultDetails[0] != NULLCHAR) {
10653         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10654                 PGNResult(gameInfo.result));
10655     } else {
10656         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10657     }
10658
10659     fclose(f);
10660     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10661     return TRUE;
10662 }
10663
10664 /* Save game in old style and close the file */
10665 int
10666 SaveGameOldStyle(f)
10667      FILE *f;
10668 {
10669     int i, offset;
10670     time_t tm;
10671     
10672     tm = time((time_t *) NULL);
10673     
10674     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10675     PrintOpponents(f);
10676     
10677     if (backwardMostMove > 0 || startedFromSetupPosition) {
10678         fprintf(f, "\n[--------------\n");
10679         PrintPosition(f, backwardMostMove);
10680         fprintf(f, "--------------]\n");
10681     } else {
10682         fprintf(f, "\n");
10683     }
10684
10685     i = backwardMostMove;
10686     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10687
10688     while (i < forwardMostMove) {
10689         if (commentList[i] != NULL) {
10690             fprintf(f, "[%s]\n", commentList[i]);
10691         }
10692
10693         if ((i % 2) == 1) {
10694             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10695             i++;
10696         } else {
10697             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10698             i++;
10699             if (commentList[i] != NULL) {
10700                 fprintf(f, "\n");
10701                 continue;
10702             }
10703             if (i >= forwardMostMove) {
10704                 fprintf(f, "\n");
10705                 break;
10706             }
10707             fprintf(f, "%s\n", parseList[i]);
10708             i++;
10709         }
10710     }
10711     
10712     if (commentList[i] != NULL) {
10713         fprintf(f, "[%s]\n", commentList[i]);
10714     }
10715
10716     /* This isn't really the old style, but it's close enough */
10717     if (gameInfo.resultDetails != NULL &&
10718         gameInfo.resultDetails[0] != NULLCHAR) {
10719         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10720                 gameInfo.resultDetails);
10721     } else {
10722         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10723     }
10724
10725     fclose(f);
10726     return TRUE;
10727 }
10728
10729 /* Save the current game to open file f and close the file */
10730 int
10731 SaveGame(f, dummy, dummy2)
10732      FILE *f;
10733      int dummy;
10734      char *dummy2;
10735 {
10736     if (gameMode == EditPosition) EditPositionDone(TRUE);
10737     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10738     if (appData.oldSaveStyle)
10739       return SaveGameOldStyle(f);
10740     else
10741       return SaveGamePGN(f);
10742 }
10743
10744 /* Save the current position to the given file */
10745 int
10746 SavePositionToFile(filename)
10747      char *filename;
10748 {
10749     FILE *f;
10750     char buf[MSG_SIZ];
10751
10752     if (strcmp(filename, "-") == 0) {
10753         return SavePosition(stdout, 0, NULL);
10754     } else {
10755         f = fopen(filename, "a");
10756         if (f == NULL) {
10757             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10758             DisplayError(buf, errno);
10759             return FALSE;
10760         } else {
10761             SavePosition(f, 0, NULL);
10762             return TRUE;
10763         }
10764     }
10765 }
10766
10767 /* Save the current position to the given open file and close the file */
10768 int
10769 SavePosition(f, dummy, dummy2)
10770      FILE *f;
10771      int dummy;
10772      char *dummy2;
10773 {
10774     time_t tm;
10775     char *fen;
10776     
10777     if (gameMode == EditPosition) EditPositionDone(TRUE);
10778     if (appData.oldSaveStyle) {
10779         tm = time((time_t *) NULL);
10780     
10781         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10782         PrintOpponents(f);
10783         fprintf(f, "[--------------\n");
10784         PrintPosition(f, currentMove);
10785         fprintf(f, "--------------]\n");
10786     } else {
10787         fen = PositionToFEN(currentMove, NULL);
10788         fprintf(f, "%s\n", fen);
10789         free(fen);
10790     }
10791     fclose(f);
10792     return TRUE;
10793 }
10794
10795 void
10796 ReloadCmailMsgEvent(unregister)
10797      int unregister;
10798 {
10799 #if !WIN32
10800     static char *inFilename = NULL;
10801     static char *outFilename;
10802     int i;
10803     struct stat inbuf, outbuf;
10804     int status;
10805     
10806     /* Any registered moves are unregistered if unregister is set, */
10807     /* i.e. invoked by the signal handler */
10808     if (unregister) {
10809         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10810             cmailMoveRegistered[i] = FALSE;
10811             if (cmailCommentList[i] != NULL) {
10812                 free(cmailCommentList[i]);
10813                 cmailCommentList[i] = NULL;
10814             }
10815         }
10816         nCmailMovesRegistered = 0;
10817     }
10818
10819     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10820         cmailResult[i] = CMAIL_NOT_RESULT;
10821     }
10822     nCmailResults = 0;
10823
10824     if (inFilename == NULL) {
10825         /* Because the filenames are static they only get malloced once  */
10826         /* and they never get freed                                      */
10827         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10828         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10829
10830         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10831         sprintf(outFilename, "%s.out", appData.cmailGameName);
10832     }
10833     
10834     status = stat(outFilename, &outbuf);
10835     if (status < 0) {
10836         cmailMailedMove = FALSE;
10837     } else {
10838         status = stat(inFilename, &inbuf);
10839         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10840     }
10841     
10842     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10843        counts the games, notes how each one terminated, etc.
10844        
10845        It would be nice to remove this kludge and instead gather all
10846        the information while building the game list.  (And to keep it
10847        in the game list nodes instead of having a bunch of fixed-size
10848        parallel arrays.)  Note this will require getting each game's
10849        termination from the PGN tags, as the game list builder does
10850        not process the game moves.  --mann
10851        */
10852     cmailMsgLoaded = TRUE;
10853     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10854     
10855     /* Load first game in the file or popup game menu */
10856     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10857
10858 #endif /* !WIN32 */
10859     return;
10860 }
10861
10862 int
10863 RegisterMove()
10864 {
10865     FILE *f;
10866     char string[MSG_SIZ];
10867
10868     if (   cmailMailedMove
10869         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10870         return TRUE;            /* Allow free viewing  */
10871     }
10872
10873     /* Unregister move to ensure that we don't leave RegisterMove        */
10874     /* with the move registered when the conditions for registering no   */
10875     /* longer hold                                                       */
10876     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10877         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10878         nCmailMovesRegistered --;
10879
10880         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10881           {
10882               free(cmailCommentList[lastLoadGameNumber - 1]);
10883               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10884           }
10885     }
10886
10887     if (cmailOldMove == -1) {
10888         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10889         return FALSE;
10890     }
10891
10892     if (currentMove > cmailOldMove + 1) {
10893         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10894         return FALSE;
10895     }
10896
10897     if (currentMove < cmailOldMove) {
10898         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10899         return FALSE;
10900     }
10901
10902     if (forwardMostMove > currentMove) {
10903         /* Silently truncate extra moves */
10904         TruncateGame();
10905     }
10906
10907     if (   (currentMove == cmailOldMove + 1)
10908         || (   (currentMove == cmailOldMove)
10909             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10910                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10911         if (gameInfo.result != GameUnfinished) {
10912             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10913         }
10914
10915         if (commentList[currentMove] != NULL) {
10916             cmailCommentList[lastLoadGameNumber - 1]
10917               = StrSave(commentList[currentMove]);
10918         }
10919         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10920
10921         if (appData.debugMode)
10922           fprintf(debugFP, "Saving %s for game %d\n",
10923                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10924
10925         sprintf(string,
10926                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10927         
10928         f = fopen(string, "w");
10929         if (appData.oldSaveStyle) {
10930             SaveGameOldStyle(f); /* also closes the file */
10931             
10932             sprintf(string, "%s.pos.out", appData.cmailGameName);
10933             f = fopen(string, "w");
10934             SavePosition(f, 0, NULL); /* also closes the file */
10935         } else {
10936             fprintf(f, "{--------------\n");
10937             PrintPosition(f, currentMove);
10938             fprintf(f, "--------------}\n\n");
10939             
10940             SaveGame(f, 0, NULL); /* also closes the file*/
10941         }
10942         
10943         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10944         nCmailMovesRegistered ++;
10945     } else if (nCmailGames == 1) {
10946         DisplayError(_("You have not made a move yet"), 0);
10947         return FALSE;
10948     }
10949
10950     return TRUE;
10951 }
10952
10953 void
10954 MailMoveEvent()
10955 {
10956 #if !WIN32
10957     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10958     FILE *commandOutput;
10959     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10960     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10961     int nBuffers;
10962     int i;
10963     int archived;
10964     char *arcDir;
10965
10966     if (! cmailMsgLoaded) {
10967         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10968         return;
10969     }
10970
10971     if (nCmailGames == nCmailResults) {
10972         DisplayError(_("No unfinished games"), 0);
10973         return;
10974     }
10975
10976 #if CMAIL_PROHIBIT_REMAIL
10977     if (cmailMailedMove) {
10978         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);
10979         DisplayError(msg, 0);
10980         return;
10981     }
10982 #endif
10983
10984     if (! (cmailMailedMove || RegisterMove())) return;
10985     
10986     if (   cmailMailedMove
10987         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10988         sprintf(string, partCommandString,
10989                 appData.debugMode ? " -v" : "", appData.cmailGameName);
10990         commandOutput = popen(string, "r");
10991
10992         if (commandOutput == NULL) {
10993             DisplayError(_("Failed to invoke cmail"), 0);
10994         } else {
10995             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10996                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10997             }
10998             if (nBuffers > 1) {
10999                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11000                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11001                 nBytes = MSG_SIZ - 1;
11002             } else {
11003                 (void) memcpy(msg, buffer, nBytes);
11004             }
11005             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11006
11007             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11008                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11009
11010                 archived = TRUE;
11011                 for (i = 0; i < nCmailGames; i ++) {
11012                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11013                         archived = FALSE;
11014                     }
11015                 }
11016                 if (   archived
11017                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11018                         != NULL)) {
11019                     sprintf(buffer, "%s/%s.%s.archive",
11020                             arcDir,
11021                             appData.cmailGameName,
11022                             gameInfo.date);
11023                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11024                     cmailMsgLoaded = FALSE;
11025                 }
11026             }
11027
11028             DisplayInformation(msg);
11029             pclose(commandOutput);
11030         }
11031     } else {
11032         if ((*cmailMsg) != '\0') {
11033             DisplayInformation(cmailMsg);
11034         }
11035     }
11036
11037     return;
11038 #endif /* !WIN32 */
11039 }
11040
11041 char *
11042 CmailMsg()
11043 {
11044 #if WIN32
11045     return NULL;
11046 #else
11047     int  prependComma = 0;
11048     char number[5];
11049     char string[MSG_SIZ];       /* Space for game-list */
11050     int  i;
11051     
11052     if (!cmailMsgLoaded) return "";
11053
11054     if (cmailMailedMove) {
11055         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11056     } else {
11057         /* Create a list of games left */
11058         sprintf(string, "[");
11059         for (i = 0; i < nCmailGames; i ++) {
11060             if (! (   cmailMoveRegistered[i]
11061                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11062                 if (prependComma) {
11063                     sprintf(number, ",%d", i + 1);
11064                 } else {
11065                     sprintf(number, "%d", i + 1);
11066                     prependComma = 1;
11067                 }
11068                 
11069                 strcat(string, number);
11070             }
11071         }
11072         strcat(string, "]");
11073
11074         if (nCmailMovesRegistered + nCmailResults == 0) {
11075             switch (nCmailGames) {
11076               case 1:
11077                 sprintf(cmailMsg,
11078                         _("Still need to make move for game\n"));
11079                 break;
11080                 
11081               case 2:
11082                 sprintf(cmailMsg,
11083                         _("Still need to make moves for both games\n"));
11084                 break;
11085                 
11086               default:
11087                 sprintf(cmailMsg,
11088                         _("Still need to make moves for all %d games\n"),
11089                         nCmailGames);
11090                 break;
11091             }
11092         } else {
11093             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11094               case 1:
11095                 sprintf(cmailMsg,
11096                         _("Still need to make a move for game %s\n"),
11097                         string);
11098                 break;
11099                 
11100               case 0:
11101                 if (nCmailResults == nCmailGames) {
11102                     sprintf(cmailMsg, _("No unfinished games\n"));
11103                 } else {
11104                     sprintf(cmailMsg, _("Ready to send mail\n"));
11105                 }
11106                 break;
11107                 
11108               default:
11109                 sprintf(cmailMsg,
11110                         _("Still need to make moves for games %s\n"),
11111                         string);
11112             }
11113         }
11114     }
11115     return cmailMsg;
11116 #endif /* WIN32 */
11117 }
11118
11119 void
11120 ResetGameEvent()
11121 {
11122     if (gameMode == Training)
11123       SetTrainingModeOff();
11124
11125     Reset(TRUE, TRUE);
11126     cmailMsgLoaded = FALSE;
11127     if (appData.icsActive) {
11128       SendToICS(ics_prefix);
11129       SendToICS("refresh\n");
11130     }
11131 }
11132
11133 void
11134 ExitEvent(status)
11135      int status;
11136 {
11137     exiting++;
11138     if (exiting > 2) {
11139       /* Give up on clean exit */
11140       exit(status);
11141     }
11142     if (exiting > 1) {
11143       /* Keep trying for clean exit */
11144       return;
11145     }
11146
11147     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11148
11149     if (telnetISR != NULL) {
11150       RemoveInputSource(telnetISR);
11151     }
11152     if (icsPR != NoProc) {
11153       DestroyChildProcess(icsPR, TRUE);
11154     }
11155
11156     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11157     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11158
11159     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11160     /* make sure this other one finishes before killing it!                  */
11161     if(endingGame) { int count = 0;
11162         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11163         while(endingGame && count++ < 10) DoSleep(1);
11164         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11165     }
11166
11167     /* Kill off chess programs */
11168     if (first.pr != NoProc) {
11169         ExitAnalyzeMode();
11170         
11171         DoSleep( appData.delayBeforeQuit );
11172         SendToProgram("quit\n", &first);
11173         DoSleep( appData.delayAfterQuit );
11174         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11175     }
11176     if (second.pr != NoProc) {
11177         DoSleep( appData.delayBeforeQuit );
11178         SendToProgram("quit\n", &second);
11179         DoSleep( appData.delayAfterQuit );
11180         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11181     }
11182     if (first.isr != NULL) {
11183         RemoveInputSource(first.isr);
11184     }
11185     if (second.isr != NULL) {
11186         RemoveInputSource(second.isr);
11187     }
11188
11189     ShutDownFrontEnd();
11190     exit(status);
11191 }
11192
11193 void
11194 PauseEvent()
11195 {
11196     if (appData.debugMode)
11197         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11198     if (pausing) {
11199         pausing = FALSE;
11200         ModeHighlight();
11201         if (gameMode == MachinePlaysWhite ||
11202             gameMode == MachinePlaysBlack) {
11203             StartClocks();
11204         } else {
11205             DisplayBothClocks();
11206         }
11207         if (gameMode == PlayFromGameFile) {
11208             if (appData.timeDelay >= 0) 
11209                 AutoPlayGameLoop();
11210         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11211             Reset(FALSE, TRUE);
11212             SendToICS(ics_prefix);
11213             SendToICS("refresh\n");
11214         } else if (currentMove < forwardMostMove) {
11215             ForwardInner(forwardMostMove);
11216         }
11217         pauseExamInvalid = FALSE;
11218     } else {
11219         switch (gameMode) {
11220           default:
11221             return;
11222           case IcsExamining:
11223             pauseExamForwardMostMove = forwardMostMove;
11224             pauseExamInvalid = FALSE;
11225             /* fall through */
11226           case IcsObserving:
11227           case IcsPlayingWhite:
11228           case IcsPlayingBlack:
11229             pausing = TRUE;
11230             ModeHighlight();
11231             return;
11232           case PlayFromGameFile:
11233             (void) StopLoadGameTimer();
11234             pausing = TRUE;
11235             ModeHighlight();
11236             break;
11237           case BeginningOfGame:
11238             if (appData.icsActive) return;
11239             /* else fall through */
11240           case MachinePlaysWhite:
11241           case MachinePlaysBlack:
11242           case TwoMachinesPlay:
11243             if (forwardMostMove == 0)
11244               return;           /* don't pause if no one has moved */
11245             if ((gameMode == MachinePlaysWhite &&
11246                  !WhiteOnMove(forwardMostMove)) ||
11247                 (gameMode == MachinePlaysBlack &&
11248                  WhiteOnMove(forwardMostMove))) {
11249                 StopClocks();
11250             }
11251             pausing = TRUE;
11252             ModeHighlight();
11253             break;
11254         }
11255     }
11256 }
11257
11258 void
11259 EditCommentEvent()
11260 {
11261     char title[MSG_SIZ];
11262
11263     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11264         strcpy(title, _("Edit comment"));
11265     } else {
11266         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11267                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11268                 parseList[currentMove - 1]);
11269     }
11270
11271     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11272 }
11273
11274
11275 void
11276 EditTagsEvent()
11277 {
11278     char *tags = PGNTags(&gameInfo);
11279     EditTagsPopUp(tags);
11280     free(tags);
11281 }
11282
11283 void
11284 AnalyzeModeEvent()
11285 {
11286     if (appData.noChessProgram || gameMode == AnalyzeMode)
11287       return;
11288
11289     if (gameMode != AnalyzeFile) {
11290         if (!appData.icsEngineAnalyze) {
11291                EditGameEvent();
11292                if (gameMode != EditGame) return;
11293         }
11294         ResurrectChessProgram();
11295         SendToProgram("analyze\n", &first);
11296         first.analyzing = TRUE;
11297         /*first.maybeThinking = TRUE;*/
11298         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11299         EngineOutputPopUp();
11300     }
11301     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11302     pausing = FALSE;
11303     ModeHighlight();
11304     SetGameInfo();
11305
11306     StartAnalysisClock();
11307     GetTimeMark(&lastNodeCountTime);
11308     lastNodeCount = 0;
11309 }
11310
11311 void
11312 AnalyzeFileEvent()
11313 {
11314     if (appData.noChessProgram || gameMode == AnalyzeFile)
11315       return;
11316
11317     if (gameMode != AnalyzeMode) {
11318         EditGameEvent();
11319         if (gameMode != EditGame) return;
11320         ResurrectChessProgram();
11321         SendToProgram("analyze\n", &first);
11322         first.analyzing = TRUE;
11323         /*first.maybeThinking = TRUE;*/
11324         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11325         EngineOutputPopUp();
11326     }
11327     gameMode = AnalyzeFile;
11328     pausing = FALSE;
11329     ModeHighlight();
11330     SetGameInfo();
11331
11332     StartAnalysisClock();
11333     GetTimeMark(&lastNodeCountTime);
11334     lastNodeCount = 0;
11335 }
11336
11337 void
11338 MachineWhiteEvent()
11339 {
11340     char buf[MSG_SIZ];
11341     char *bookHit = NULL;
11342
11343     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11344       return;
11345
11346
11347     if (gameMode == PlayFromGameFile || 
11348         gameMode == TwoMachinesPlay  || 
11349         gameMode == Training         || 
11350         gameMode == AnalyzeMode      || 
11351         gameMode == EndOfGame)
11352         EditGameEvent();
11353
11354     if (gameMode == EditPosition) 
11355         EditPositionDone(TRUE);
11356
11357     if (!WhiteOnMove(currentMove)) {
11358         DisplayError(_("It is not White's turn"), 0);
11359         return;
11360     }
11361   
11362     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11363       ExitAnalyzeMode();
11364
11365     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11366         gameMode == AnalyzeFile)
11367         TruncateGame();
11368
11369     ResurrectChessProgram();    /* in case it isn't running */
11370     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11371         gameMode = MachinePlaysWhite;
11372         ResetClocks();
11373     } else
11374     gameMode = MachinePlaysWhite;
11375     pausing = FALSE;
11376     ModeHighlight();
11377     SetGameInfo();
11378     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11379     DisplayTitle(buf);
11380     if (first.sendName) {
11381       sprintf(buf, "name %s\n", gameInfo.black);
11382       SendToProgram(buf, &first);
11383     }
11384     if (first.sendTime) {
11385       if (first.useColors) {
11386         SendToProgram("black\n", &first); /*gnu kludge*/
11387       }
11388       SendTimeRemaining(&first, TRUE);
11389     }
11390     if (first.useColors) {
11391       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11392     }
11393     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11394     SetMachineThinkingEnables();
11395     first.maybeThinking = TRUE;
11396     StartClocks();
11397     firstMove = FALSE;
11398
11399     if (appData.autoFlipView && !flipView) {
11400       flipView = !flipView;
11401       DrawPosition(FALSE, NULL);
11402       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11403     }
11404
11405     if(bookHit) { // [HGM] book: simulate book reply
11406         static char bookMove[MSG_SIZ]; // a bit generous?
11407
11408         programStats.nodes = programStats.depth = programStats.time = 
11409         programStats.score = programStats.got_only_move = 0;
11410         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11411
11412         strcpy(bookMove, "move ");
11413         strcat(bookMove, bookHit);
11414         HandleMachineMove(bookMove, &first);
11415     }
11416 }
11417
11418 void
11419 MachineBlackEvent()
11420 {
11421     char buf[MSG_SIZ];
11422    char *bookHit = NULL;
11423
11424     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11425         return;
11426
11427
11428     if (gameMode == PlayFromGameFile || 
11429         gameMode == TwoMachinesPlay  || 
11430         gameMode == Training         || 
11431         gameMode == AnalyzeMode      || 
11432         gameMode == EndOfGame)
11433         EditGameEvent();
11434
11435     if (gameMode == EditPosition) 
11436         EditPositionDone(TRUE);
11437
11438     if (WhiteOnMove(currentMove)) {
11439         DisplayError(_("It is not Black's turn"), 0);
11440         return;
11441     }
11442     
11443     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11444       ExitAnalyzeMode();
11445
11446     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11447         gameMode == AnalyzeFile)
11448         TruncateGame();
11449
11450     ResurrectChessProgram();    /* in case it isn't running */
11451     gameMode = MachinePlaysBlack;
11452     pausing = FALSE;
11453     ModeHighlight();
11454     SetGameInfo();
11455     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11456     DisplayTitle(buf);
11457     if (first.sendName) {
11458       sprintf(buf, "name %s\n", gameInfo.white);
11459       SendToProgram(buf, &first);
11460     }
11461     if (first.sendTime) {
11462       if (first.useColors) {
11463         SendToProgram("white\n", &first); /*gnu kludge*/
11464       }
11465       SendTimeRemaining(&first, FALSE);
11466     }
11467     if (first.useColors) {
11468       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11469     }
11470     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11471     SetMachineThinkingEnables();
11472     first.maybeThinking = TRUE;
11473     StartClocks();
11474
11475     if (appData.autoFlipView && flipView) {
11476       flipView = !flipView;
11477       DrawPosition(FALSE, NULL);
11478       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11479     }
11480     if(bookHit) { // [HGM] book: simulate book reply
11481         static char bookMove[MSG_SIZ]; // a bit generous?
11482
11483         programStats.nodes = programStats.depth = programStats.time = 
11484         programStats.score = programStats.got_only_move = 0;
11485         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11486
11487         strcpy(bookMove, "move ");
11488         strcat(bookMove, bookHit);
11489         HandleMachineMove(bookMove, &first);
11490     }
11491 }
11492
11493
11494 void
11495 DisplayTwoMachinesTitle()
11496 {
11497     char buf[MSG_SIZ];
11498     if (appData.matchGames > 0) {
11499         if (first.twoMachinesColor[0] == 'w') {
11500             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11501                     gameInfo.white, gameInfo.black,
11502                     first.matchWins, second.matchWins,
11503                     matchGame - 1 - (first.matchWins + second.matchWins));
11504         } else {
11505             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11506                     gameInfo.white, gameInfo.black,
11507                     second.matchWins, first.matchWins,
11508                     matchGame - 1 - (first.matchWins + second.matchWins));
11509         }
11510     } else {
11511         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11512     }
11513     DisplayTitle(buf);
11514 }
11515
11516 void
11517 TwoMachinesEvent P((void))
11518 {
11519     int i;
11520     char buf[MSG_SIZ];
11521     ChessProgramState *onmove;
11522     char *bookHit = NULL;
11523     
11524     if (appData.noChessProgram) return;
11525
11526     switch (gameMode) {
11527       case TwoMachinesPlay:
11528         return;
11529       case MachinePlaysWhite:
11530       case MachinePlaysBlack:
11531         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11532             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11533             return;
11534         }
11535         /* fall through */
11536       case BeginningOfGame:
11537       case PlayFromGameFile:
11538       case EndOfGame:
11539         EditGameEvent();
11540         if (gameMode != EditGame) return;
11541         break;
11542       case EditPosition:
11543         EditPositionDone(TRUE);
11544         break;
11545       case AnalyzeMode:
11546       case AnalyzeFile:
11547         ExitAnalyzeMode();
11548         break;
11549       case EditGame:
11550       default:
11551         break;
11552     }
11553
11554 //    forwardMostMove = currentMove;
11555     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11556     ResurrectChessProgram();    /* in case first program isn't running */
11557
11558     if (second.pr == NULL) {
11559         StartChessProgram(&second);
11560         if (second.protocolVersion == 1) {
11561           TwoMachinesEventIfReady();
11562         } else {
11563           /* kludge: allow timeout for initial "feature" command */
11564           FreezeUI();
11565           DisplayMessage("", _("Starting second chess program"));
11566           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11567         }
11568         return;
11569     }
11570     DisplayMessage("", "");
11571     InitChessProgram(&second, FALSE);
11572     SendToProgram("force\n", &second);
11573     if (startedFromSetupPosition) {
11574         SendBoard(&second, backwardMostMove);
11575     if (appData.debugMode) {
11576         fprintf(debugFP, "Two Machines\n");
11577     }
11578     }
11579     for (i = backwardMostMove; i < forwardMostMove; i++) {
11580         SendMoveToProgram(i, &second);
11581     }
11582
11583     gameMode = TwoMachinesPlay;
11584     pausing = FALSE;
11585     ModeHighlight();
11586     SetGameInfo();
11587     DisplayTwoMachinesTitle();
11588     firstMove = TRUE;
11589     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11590         onmove = &first;
11591     } else {
11592         onmove = &second;
11593     }
11594
11595     SendToProgram(first.computerString, &first);
11596     if (first.sendName) {
11597       sprintf(buf, "name %s\n", second.tidy);
11598       SendToProgram(buf, &first);
11599     }
11600     SendToProgram(second.computerString, &second);
11601     if (second.sendName) {
11602       sprintf(buf, "name %s\n", first.tidy);
11603       SendToProgram(buf, &second);
11604     }
11605
11606     ResetClocks();
11607     if (!first.sendTime || !second.sendTime) {
11608         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11609         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11610     }
11611     if (onmove->sendTime) {
11612       if (onmove->useColors) {
11613         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11614       }
11615       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11616     }
11617     if (onmove->useColors) {
11618       SendToProgram(onmove->twoMachinesColor, onmove);
11619     }
11620     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11621 //    SendToProgram("go\n", onmove);
11622     onmove->maybeThinking = TRUE;
11623     SetMachineThinkingEnables();
11624
11625     StartClocks();
11626
11627     if(bookHit) { // [HGM] book: simulate book reply
11628         static char bookMove[MSG_SIZ]; // a bit generous?
11629
11630         programStats.nodes = programStats.depth = programStats.time = 
11631         programStats.score = programStats.got_only_move = 0;
11632         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11633
11634         strcpy(bookMove, "move ");
11635         strcat(bookMove, bookHit);
11636         savedMessage = bookMove; // args for deferred call
11637         savedState = onmove;
11638         ScheduleDelayedEvent(DeferredBookMove, 1);
11639     }
11640 }
11641
11642 void
11643 TrainingEvent()
11644 {
11645     if (gameMode == Training) {
11646       SetTrainingModeOff();
11647       gameMode = PlayFromGameFile;
11648       DisplayMessage("", _("Training mode off"));
11649     } else {
11650       gameMode = Training;
11651       animateTraining = appData.animate;
11652
11653       /* make sure we are not already at the end of the game */
11654       if (currentMove < forwardMostMove) {
11655         SetTrainingModeOn();
11656         DisplayMessage("", _("Training mode on"));
11657       } else {
11658         gameMode = PlayFromGameFile;
11659         DisplayError(_("Already at end of game"), 0);
11660       }
11661     }
11662     ModeHighlight();
11663 }
11664
11665 void
11666 IcsClientEvent()
11667 {
11668     if (!appData.icsActive) return;
11669     switch (gameMode) {
11670       case IcsPlayingWhite:
11671       case IcsPlayingBlack:
11672       case IcsObserving:
11673       case IcsIdle:
11674       case BeginningOfGame:
11675       case IcsExamining:
11676         return;
11677
11678       case EditGame:
11679         break;
11680
11681       case EditPosition:
11682         EditPositionDone(TRUE);
11683         break;
11684
11685       case AnalyzeMode:
11686       case AnalyzeFile:
11687         ExitAnalyzeMode();
11688         break;
11689         
11690       default:
11691         EditGameEvent();
11692         break;
11693     }
11694
11695     gameMode = IcsIdle;
11696     ModeHighlight();
11697     return;
11698 }
11699
11700
11701 void
11702 EditGameEvent()
11703 {
11704     int i;
11705
11706     switch (gameMode) {
11707       case Training:
11708         SetTrainingModeOff();
11709         break;
11710       case MachinePlaysWhite:
11711       case MachinePlaysBlack:
11712       case BeginningOfGame:
11713         SendToProgram("force\n", &first);
11714         SetUserThinkingEnables();
11715         break;
11716       case PlayFromGameFile:
11717         (void) StopLoadGameTimer();
11718         if (gameFileFP != NULL) {
11719             gameFileFP = NULL;
11720         }
11721         break;
11722       case EditPosition:
11723         EditPositionDone(TRUE);
11724         break;
11725       case AnalyzeMode:
11726       case AnalyzeFile:
11727         ExitAnalyzeMode();
11728         SendToProgram("force\n", &first);
11729         break;
11730       case TwoMachinesPlay:
11731         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11732         ResurrectChessProgram();
11733         SetUserThinkingEnables();
11734         break;
11735       case EndOfGame:
11736         ResurrectChessProgram();
11737         break;
11738       case IcsPlayingBlack:
11739       case IcsPlayingWhite:
11740         DisplayError(_("Warning: You are still playing a game"), 0);
11741         break;
11742       case IcsObserving:
11743         DisplayError(_("Warning: You are still observing a game"), 0);
11744         break;
11745       case IcsExamining:
11746         DisplayError(_("Warning: You are still examining a game"), 0);
11747         break;
11748       case IcsIdle:
11749         break;
11750       case EditGame:
11751       default:
11752         return;
11753     }
11754     
11755     pausing = FALSE;
11756     StopClocks();
11757     first.offeredDraw = second.offeredDraw = 0;
11758
11759     if (gameMode == PlayFromGameFile) {
11760         whiteTimeRemaining = timeRemaining[0][currentMove];
11761         blackTimeRemaining = timeRemaining[1][currentMove];
11762         DisplayTitle("");
11763     }
11764
11765     if (gameMode == MachinePlaysWhite ||
11766         gameMode == MachinePlaysBlack ||
11767         gameMode == TwoMachinesPlay ||
11768         gameMode == EndOfGame) {
11769         i = forwardMostMove;
11770         while (i > currentMove) {
11771             SendToProgram("undo\n", &first);
11772             i--;
11773         }
11774         whiteTimeRemaining = timeRemaining[0][currentMove];
11775         blackTimeRemaining = timeRemaining[1][currentMove];
11776         DisplayBothClocks();
11777         if (whiteFlag || blackFlag) {
11778             whiteFlag = blackFlag = 0;
11779         }
11780         DisplayTitle("");
11781     }           
11782     
11783     gameMode = EditGame;
11784     ModeHighlight();
11785     SetGameInfo();
11786 }
11787
11788
11789 void
11790 EditPositionEvent()
11791 {
11792     if (gameMode == EditPosition) {
11793         EditGameEvent();
11794         return;
11795     }
11796     
11797     EditGameEvent();
11798     if (gameMode != EditGame) return;
11799     
11800     gameMode = EditPosition;
11801     ModeHighlight();
11802     SetGameInfo();
11803     if (currentMove > 0)
11804       CopyBoard(boards[0], boards[currentMove]);
11805     
11806     blackPlaysFirst = !WhiteOnMove(currentMove);
11807     ResetClocks();
11808     currentMove = forwardMostMove = backwardMostMove = 0;
11809     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11810     DisplayMove(-1);
11811 }
11812
11813 void
11814 ExitAnalyzeMode()
11815 {
11816     /* [DM] icsEngineAnalyze - possible call from other functions */
11817     if (appData.icsEngineAnalyze) {
11818         appData.icsEngineAnalyze = FALSE;
11819
11820         DisplayMessage("",_("Close ICS engine analyze..."));
11821     }
11822     if (first.analysisSupport && first.analyzing) {
11823       SendToProgram("exit\n", &first);
11824       first.analyzing = FALSE;
11825     }
11826     thinkOutput[0] = NULLCHAR;
11827 }
11828
11829 void
11830 EditPositionDone(Boolean fakeRights)
11831 {
11832     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11833
11834     startedFromSetupPosition = TRUE;
11835     InitChessProgram(&first, FALSE);
11836     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11837       boards[0][EP_STATUS] = EP_NONE;
11838       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11839     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11840         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11841         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11842       } else boards[0][CASTLING][2] = NoRights;
11843     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11844         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11845         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11846       } else boards[0][CASTLING][5] = NoRights;
11847     }
11848     SendToProgram("force\n", &first);
11849     if (blackPlaysFirst) {
11850         strcpy(moveList[0], "");
11851         strcpy(parseList[0], "");
11852         currentMove = forwardMostMove = backwardMostMove = 1;
11853         CopyBoard(boards[1], boards[0]);
11854     } else {
11855         currentMove = forwardMostMove = backwardMostMove = 0;
11856     }
11857     SendBoard(&first, forwardMostMove);
11858     if (appData.debugMode) {
11859         fprintf(debugFP, "EditPosDone\n");
11860     }
11861     DisplayTitle("");
11862     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11863     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11864     gameMode = EditGame;
11865     ModeHighlight();
11866     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11867     ClearHighlights(); /* [AS] */
11868 }
11869
11870 /* Pause for `ms' milliseconds */
11871 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11872 void
11873 TimeDelay(ms)
11874      long ms;
11875 {
11876     TimeMark m1, m2;
11877
11878     GetTimeMark(&m1);
11879     do {
11880         GetTimeMark(&m2);
11881     } while (SubtractTimeMarks(&m2, &m1) < ms);
11882 }
11883
11884 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11885 void
11886 SendMultiLineToICS(buf)
11887      char *buf;
11888 {
11889     char temp[MSG_SIZ+1], *p;
11890     int len;
11891
11892     len = strlen(buf);
11893     if (len > MSG_SIZ)
11894       len = MSG_SIZ;
11895   
11896     strncpy(temp, buf, len);
11897     temp[len] = 0;
11898
11899     p = temp;
11900     while (*p) {
11901         if (*p == '\n' || *p == '\r')
11902           *p = ' ';
11903         ++p;
11904     }
11905
11906     strcat(temp, "\n");
11907     SendToICS(temp);
11908     SendToPlayer(temp, strlen(temp));
11909 }
11910
11911 void
11912 SetWhiteToPlayEvent()
11913 {
11914     if (gameMode == EditPosition) {
11915         blackPlaysFirst = FALSE;
11916         DisplayBothClocks();    /* works because currentMove is 0 */
11917     } else if (gameMode == IcsExamining) {
11918         SendToICS(ics_prefix);
11919         SendToICS("tomove white\n");
11920     }
11921 }
11922
11923 void
11924 SetBlackToPlayEvent()
11925 {
11926     if (gameMode == EditPosition) {
11927         blackPlaysFirst = TRUE;
11928         currentMove = 1;        /* kludge */
11929         DisplayBothClocks();
11930         currentMove = 0;
11931     } else if (gameMode == IcsExamining) {
11932         SendToICS(ics_prefix);
11933         SendToICS("tomove black\n");
11934     }
11935 }
11936
11937 void
11938 EditPositionMenuEvent(selection, x, y)
11939      ChessSquare selection;
11940      int x, y;
11941 {
11942     char buf[MSG_SIZ];
11943     ChessSquare piece = boards[0][y][x];
11944
11945     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11946
11947     switch (selection) {
11948       case ClearBoard:
11949         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11950             SendToICS(ics_prefix);
11951             SendToICS("bsetup clear\n");
11952         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11953             SendToICS(ics_prefix);
11954             SendToICS("clearboard\n");
11955         } else {
11956             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11957                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11958                 for (y = 0; y < BOARD_HEIGHT; y++) {
11959                     if (gameMode == IcsExamining) {
11960                         if (boards[currentMove][y][x] != EmptySquare) {
11961                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11962                                     AAA + x, ONE + y);
11963                             SendToICS(buf);
11964                         }
11965                     } else {
11966                         boards[0][y][x] = p;
11967                     }
11968                 }
11969             }
11970         }
11971         if (gameMode == EditPosition) {
11972             DrawPosition(FALSE, boards[0]);
11973         }
11974         break;
11975
11976       case WhitePlay:
11977         SetWhiteToPlayEvent();
11978         break;
11979
11980       case BlackPlay:
11981         SetBlackToPlayEvent();
11982         break;
11983
11984       case EmptySquare:
11985         if (gameMode == IcsExamining) {
11986             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11987             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11988             SendToICS(buf);
11989         } else {
11990             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11991                 if(x == BOARD_LEFT-2) {
11992                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11993                     boards[0][y][1] = 0;
11994                 } else
11995                 if(x == BOARD_RGHT+1) {
11996                     if(y >= gameInfo.holdingsSize) break;
11997                     boards[0][y][BOARD_WIDTH-2] = 0;
11998                 } else break;
11999             }
12000             boards[0][y][x] = EmptySquare;
12001             DrawPosition(FALSE, boards[0]);
12002         }
12003         break;
12004
12005       case PromotePiece:
12006         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12007            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12008             selection = (ChessSquare) (PROMOTED piece);
12009         } else if(piece == EmptySquare) selection = WhiteSilver;
12010         else selection = (ChessSquare)((int)piece - 1);
12011         goto defaultlabel;
12012
12013       case DemotePiece:
12014         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12015            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12016             selection = (ChessSquare) (DEMOTED piece);
12017         } else if(piece == EmptySquare) selection = BlackSilver;
12018         else selection = (ChessSquare)((int)piece + 1);       
12019         goto defaultlabel;
12020
12021       case WhiteQueen:
12022       case BlackQueen:
12023         if(gameInfo.variant == VariantShatranj ||
12024            gameInfo.variant == VariantXiangqi  ||
12025            gameInfo.variant == VariantCourier  ||
12026            gameInfo.variant == VariantMakruk     )
12027             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12028         goto defaultlabel;
12029
12030       case WhiteKing:
12031       case BlackKing:
12032         if(gameInfo.variant == VariantXiangqi)
12033             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12034         if(gameInfo.variant == VariantKnightmate)
12035             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12036       default:
12037         defaultlabel:
12038         if (gameMode == IcsExamining) {
12039             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12040             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12041                     PieceToChar(selection), AAA + x, ONE + y);
12042             SendToICS(buf);
12043         } else {
12044             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12045                 int n;
12046                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12047                     n = PieceToNumber(selection - BlackPawn);
12048                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12049                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12050                     boards[0][BOARD_HEIGHT-1-n][1]++;
12051                 } else
12052                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12053                     n = PieceToNumber(selection);
12054                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12055                     boards[0][n][BOARD_WIDTH-1] = selection;
12056                     boards[0][n][BOARD_WIDTH-2]++;
12057                 }
12058             } else
12059             boards[0][y][x] = selection;
12060             DrawPosition(TRUE, boards[0]);
12061         }
12062         break;
12063     }
12064 }
12065
12066
12067 void
12068 DropMenuEvent(selection, x, y)
12069      ChessSquare selection;
12070      int x, y;
12071 {
12072     ChessMove moveType;
12073
12074     switch (gameMode) {
12075       case IcsPlayingWhite:
12076       case MachinePlaysBlack:
12077         if (!WhiteOnMove(currentMove)) {
12078             DisplayMoveError(_("It is Black's turn"));
12079             return;
12080         }
12081         moveType = WhiteDrop;
12082         break;
12083       case IcsPlayingBlack:
12084       case MachinePlaysWhite:
12085         if (WhiteOnMove(currentMove)) {
12086             DisplayMoveError(_("It is White's turn"));
12087             return;
12088         }
12089         moveType = BlackDrop;
12090         break;
12091       case EditGame:
12092         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12093         break;
12094       default:
12095         return;
12096     }
12097
12098     if (moveType == BlackDrop && selection < BlackPawn) {
12099       selection = (ChessSquare) ((int) selection
12100                                  + (int) BlackPawn - (int) WhitePawn);
12101     }
12102     if (boards[currentMove][y][x] != EmptySquare) {
12103         DisplayMoveError(_("That square is occupied"));
12104         return;
12105     }
12106
12107     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12108 }
12109
12110 void
12111 AcceptEvent()
12112 {
12113     /* Accept a pending offer of any kind from opponent */
12114     
12115     if (appData.icsActive) {
12116         SendToICS(ics_prefix);
12117         SendToICS("accept\n");
12118     } else if (cmailMsgLoaded) {
12119         if (currentMove == cmailOldMove &&
12120             commentList[cmailOldMove] != NULL &&
12121             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12122                    "Black offers a draw" : "White offers a draw")) {
12123             TruncateGame();
12124             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12125             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12126         } else {
12127             DisplayError(_("There is no pending offer on this move"), 0);
12128             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12129         }
12130     } else {
12131         /* Not used for offers from chess program */
12132     }
12133 }
12134
12135 void
12136 DeclineEvent()
12137 {
12138     /* Decline a pending offer of any kind from opponent */
12139     
12140     if (appData.icsActive) {
12141         SendToICS(ics_prefix);
12142         SendToICS("decline\n");
12143     } else if (cmailMsgLoaded) {
12144         if (currentMove == cmailOldMove &&
12145             commentList[cmailOldMove] != NULL &&
12146             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12147                    "Black offers a draw" : "White offers a draw")) {
12148 #ifdef NOTDEF
12149             AppendComment(cmailOldMove, "Draw declined", TRUE);
12150             DisplayComment(cmailOldMove - 1, "Draw declined");
12151 #endif /*NOTDEF*/
12152         } else {
12153             DisplayError(_("There is no pending offer on this move"), 0);
12154         }
12155     } else {
12156         /* Not used for offers from chess program */
12157     }
12158 }
12159
12160 void
12161 RematchEvent()
12162 {
12163     /* Issue ICS rematch command */
12164     if (appData.icsActive) {
12165         SendToICS(ics_prefix);
12166         SendToICS("rematch\n");
12167     }
12168 }
12169
12170 void
12171 CallFlagEvent()
12172 {
12173     /* Call your opponent's flag (claim a win on time) */
12174     if (appData.icsActive) {
12175         SendToICS(ics_prefix);
12176         SendToICS("flag\n");
12177     } else {
12178         switch (gameMode) {
12179           default:
12180             return;
12181           case MachinePlaysWhite:
12182             if (whiteFlag) {
12183                 if (blackFlag)
12184                   GameEnds(GameIsDrawn, "Both players ran out of time",
12185                            GE_PLAYER);
12186                 else
12187                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12188             } else {
12189                 DisplayError(_("Your opponent is not out of time"), 0);
12190             }
12191             break;
12192           case MachinePlaysBlack:
12193             if (blackFlag) {
12194                 if (whiteFlag)
12195                   GameEnds(GameIsDrawn, "Both players ran out of time",
12196                            GE_PLAYER);
12197                 else
12198                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12199             } else {
12200                 DisplayError(_("Your opponent is not out of time"), 0);
12201             }
12202             break;
12203         }
12204     }
12205 }
12206
12207 void
12208 DrawEvent()
12209 {
12210     /* Offer draw or accept pending draw offer from opponent */
12211     
12212     if (appData.icsActive) {
12213         /* Note: tournament rules require draw offers to be
12214            made after you make your move but before you punch
12215            your clock.  Currently ICS doesn't let you do that;
12216            instead, you immediately punch your clock after making
12217            a move, but you can offer a draw at any time. */
12218         
12219         SendToICS(ics_prefix);
12220         SendToICS("draw\n");
12221         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12222     } else if (cmailMsgLoaded) {
12223         if (currentMove == cmailOldMove &&
12224             commentList[cmailOldMove] != NULL &&
12225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12226                    "Black offers a draw" : "White offers a draw")) {
12227             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12229         } else if (currentMove == cmailOldMove + 1) {
12230             char *offer = WhiteOnMove(cmailOldMove) ?
12231               "White offers a draw" : "Black offers a draw";
12232             AppendComment(currentMove, offer, TRUE);
12233             DisplayComment(currentMove - 1, offer);
12234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12235         } else {
12236             DisplayError(_("You must make your move before offering a draw"), 0);
12237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12238         }
12239     } else if (first.offeredDraw) {
12240         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12241     } else {
12242         if (first.sendDrawOffers) {
12243             SendToProgram("draw\n", &first);
12244             userOfferedDraw = TRUE;
12245         }
12246     }
12247 }
12248
12249 void
12250 AdjournEvent()
12251 {
12252     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12253     
12254     if (appData.icsActive) {
12255         SendToICS(ics_prefix);
12256         SendToICS("adjourn\n");
12257     } else {
12258         /* Currently GNU Chess doesn't offer or accept Adjourns */
12259     }
12260 }
12261
12262
12263 void
12264 AbortEvent()
12265 {
12266     /* Offer Abort or accept pending Abort offer from opponent */
12267     
12268     if (appData.icsActive) {
12269         SendToICS(ics_prefix);
12270         SendToICS("abort\n");
12271     } else {
12272         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12273     }
12274 }
12275
12276 void
12277 ResignEvent()
12278 {
12279     /* Resign.  You can do this even if it's not your turn. */
12280     
12281     if (appData.icsActive) {
12282         SendToICS(ics_prefix);
12283         SendToICS("resign\n");
12284     } else {
12285         switch (gameMode) {
12286           case MachinePlaysWhite:
12287             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12288             break;
12289           case MachinePlaysBlack:
12290             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12291             break;
12292           case EditGame:
12293             if (cmailMsgLoaded) {
12294                 TruncateGame();
12295                 if (WhiteOnMove(cmailOldMove)) {
12296                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12297                 } else {
12298                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12299                 }
12300                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12301             }
12302             break;
12303           default:
12304             break;
12305         }
12306     }
12307 }
12308
12309
12310 void
12311 StopObservingEvent()
12312 {
12313     /* Stop observing current games */
12314     SendToICS(ics_prefix);
12315     SendToICS("unobserve\n");
12316 }
12317
12318 void
12319 StopExaminingEvent()
12320 {
12321     /* Stop observing current game */
12322     SendToICS(ics_prefix);
12323     SendToICS("unexamine\n");
12324 }
12325
12326 void
12327 ForwardInner(target)
12328      int target;
12329 {
12330     int limit;
12331
12332     if (appData.debugMode)
12333         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12334                 target, currentMove, forwardMostMove);
12335
12336     if (gameMode == EditPosition)
12337       return;
12338
12339     if (gameMode == PlayFromGameFile && !pausing)
12340       PauseEvent();
12341     
12342     if (gameMode == IcsExamining && pausing)
12343       limit = pauseExamForwardMostMove;
12344     else
12345       limit = forwardMostMove;
12346     
12347     if (target > limit) target = limit;
12348
12349     if (target > 0 && moveList[target - 1][0]) {
12350         int fromX, fromY, toX, toY;
12351         toX = moveList[target - 1][2] - AAA;
12352         toY = moveList[target - 1][3] - ONE;
12353         if (moveList[target - 1][1] == '@') {
12354             if (appData.highlightLastMove) {
12355                 SetHighlights(-1, -1, toX, toY);
12356             }
12357         } else {
12358             fromX = moveList[target - 1][0] - AAA;
12359             fromY = moveList[target - 1][1] - ONE;
12360             if (target == currentMove + 1) {
12361                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12362             }
12363             if (appData.highlightLastMove) {
12364                 SetHighlights(fromX, fromY, toX, toY);
12365             }
12366         }
12367     }
12368     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12369         gameMode == Training || gameMode == PlayFromGameFile || 
12370         gameMode == AnalyzeFile) {
12371         while (currentMove < target) {
12372             SendMoveToProgram(currentMove++, &first);
12373         }
12374     } else {
12375         currentMove = target;
12376     }
12377     
12378     if (gameMode == EditGame || gameMode == EndOfGame) {
12379         whiteTimeRemaining = timeRemaining[0][currentMove];
12380         blackTimeRemaining = timeRemaining[1][currentMove];
12381     }
12382     DisplayBothClocks();
12383     DisplayMove(currentMove - 1);
12384     DrawPosition(FALSE, boards[currentMove]);
12385     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12386     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12387         DisplayComment(currentMove - 1, commentList[currentMove]);
12388     }
12389 }
12390
12391
12392 void
12393 ForwardEvent()
12394 {
12395     if (gameMode == IcsExamining && !pausing) {
12396         SendToICS(ics_prefix);
12397         SendToICS("forward\n");
12398     } else {
12399         ForwardInner(currentMove + 1);
12400     }
12401 }
12402
12403 void
12404 ToEndEvent()
12405 {
12406     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12407         /* to optimze, we temporarily turn off analysis mode while we feed
12408          * the remaining moves to the engine. Otherwise we get analysis output
12409          * after each move.
12410          */ 
12411         if (first.analysisSupport) {
12412           SendToProgram("exit\nforce\n", &first);
12413           first.analyzing = FALSE;
12414         }
12415     }
12416         
12417     if (gameMode == IcsExamining && !pausing) {
12418         SendToICS(ics_prefix);
12419         SendToICS("forward 999999\n");
12420     } else {
12421         ForwardInner(forwardMostMove);
12422     }
12423
12424     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12425         /* we have fed all the moves, so reactivate analysis mode */
12426         SendToProgram("analyze\n", &first);
12427         first.analyzing = TRUE;
12428         /*first.maybeThinking = TRUE;*/
12429         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12430     }
12431 }
12432
12433 void
12434 BackwardInner(target)
12435      int target;
12436 {
12437     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12438
12439     if (appData.debugMode)
12440         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12441                 target, currentMove, forwardMostMove);
12442
12443     if (gameMode == EditPosition) return;
12444     if (currentMove <= backwardMostMove) {
12445         ClearHighlights();
12446         DrawPosition(full_redraw, boards[currentMove]);
12447         return;
12448     }
12449     if (gameMode == PlayFromGameFile && !pausing)
12450       PauseEvent();
12451     
12452     if (moveList[target][0]) {
12453         int fromX, fromY, toX, toY;
12454         toX = moveList[target][2] - AAA;
12455         toY = moveList[target][3] - ONE;
12456         if (moveList[target][1] == '@') {
12457             if (appData.highlightLastMove) {
12458                 SetHighlights(-1, -1, toX, toY);
12459             }
12460         } else {
12461             fromX = moveList[target][0] - AAA;
12462             fromY = moveList[target][1] - ONE;
12463             if (target == currentMove - 1) {
12464                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12465             }
12466             if (appData.highlightLastMove) {
12467                 SetHighlights(fromX, fromY, toX, toY);
12468             }
12469         }
12470     }
12471     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12472         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12473         while (currentMove > target) {
12474             SendToProgram("undo\n", &first);
12475             currentMove--;
12476         }
12477     } else {
12478         currentMove = target;
12479     }
12480     
12481     if (gameMode == EditGame || gameMode == EndOfGame) {
12482         whiteTimeRemaining = timeRemaining[0][currentMove];
12483         blackTimeRemaining = timeRemaining[1][currentMove];
12484     }
12485     DisplayBothClocks();
12486     DisplayMove(currentMove - 1);
12487     DrawPosition(full_redraw, boards[currentMove]);
12488     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12489     // [HGM] PV info: routine tests if comment empty
12490     DisplayComment(currentMove - 1, commentList[currentMove]);
12491 }
12492
12493 void
12494 BackwardEvent()
12495 {
12496     if (gameMode == IcsExamining && !pausing) {
12497         SendToICS(ics_prefix);
12498         SendToICS("backward\n");
12499     } else {
12500         BackwardInner(currentMove - 1);
12501     }
12502 }
12503
12504 void
12505 ToStartEvent()
12506 {
12507     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12508         /* to optimize, we temporarily turn off analysis mode while we undo
12509          * all the moves. Otherwise we get analysis output after each undo.
12510          */ 
12511         if (first.analysisSupport) {
12512           SendToProgram("exit\nforce\n", &first);
12513           first.analyzing = FALSE;
12514         }
12515     }
12516
12517     if (gameMode == IcsExamining && !pausing) {
12518         SendToICS(ics_prefix);
12519         SendToICS("backward 999999\n");
12520     } else {
12521         BackwardInner(backwardMostMove);
12522     }
12523
12524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12525         /* we have fed all the moves, so reactivate analysis mode */
12526         SendToProgram("analyze\n", &first);
12527         first.analyzing = TRUE;
12528         /*first.maybeThinking = TRUE;*/
12529         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12530     }
12531 }
12532
12533 void
12534 ToNrEvent(int to)
12535 {
12536   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12537   if (to >= forwardMostMove) to = forwardMostMove;
12538   if (to <= backwardMostMove) to = backwardMostMove;
12539   if (to < currentMove) {
12540     BackwardInner(to);
12541   } else {
12542     ForwardInner(to);
12543   }
12544 }
12545
12546 void
12547 RevertEvent()
12548 {
12549     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12550         return;
12551     }
12552     if (gameMode != IcsExamining) {
12553         DisplayError(_("You are not examining a game"), 0);
12554         return;
12555     }
12556     if (pausing) {
12557         DisplayError(_("You can't revert while pausing"), 0);
12558         return;
12559     }
12560     SendToICS(ics_prefix);
12561     SendToICS("revert\n");
12562 }
12563
12564 void
12565 RetractMoveEvent()
12566 {
12567     switch (gameMode) {
12568       case MachinePlaysWhite:
12569       case MachinePlaysBlack:
12570         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12571             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12572             return;
12573         }
12574         if (forwardMostMove < 2) return;
12575         currentMove = forwardMostMove = forwardMostMove - 2;
12576         whiteTimeRemaining = timeRemaining[0][currentMove];
12577         blackTimeRemaining = timeRemaining[1][currentMove];
12578         DisplayBothClocks();
12579         DisplayMove(currentMove - 1);
12580         ClearHighlights();/*!! could figure this out*/
12581         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12582         SendToProgram("remove\n", &first);
12583         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12584         break;
12585
12586       case BeginningOfGame:
12587       default:
12588         break;
12589
12590       case IcsPlayingWhite:
12591       case IcsPlayingBlack:
12592         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12593             SendToICS(ics_prefix);
12594             SendToICS("takeback 2\n");
12595         } else {
12596             SendToICS(ics_prefix);
12597             SendToICS("takeback 1\n");
12598         }
12599         break;
12600     }
12601 }
12602
12603 void
12604 MoveNowEvent()
12605 {
12606     ChessProgramState *cps;
12607
12608     switch (gameMode) {
12609       case MachinePlaysWhite:
12610         if (!WhiteOnMove(forwardMostMove)) {
12611             DisplayError(_("It is your turn"), 0);
12612             return;
12613         }
12614         cps = &first;
12615         break;
12616       case MachinePlaysBlack:
12617         if (WhiteOnMove(forwardMostMove)) {
12618             DisplayError(_("It is your turn"), 0);
12619             return;
12620         }
12621         cps = &first;
12622         break;
12623       case TwoMachinesPlay:
12624         if (WhiteOnMove(forwardMostMove) ==
12625             (first.twoMachinesColor[0] == 'w')) {
12626             cps = &first;
12627         } else {
12628             cps = &second;
12629         }
12630         break;
12631       case BeginningOfGame:
12632       default:
12633         return;
12634     }
12635     SendToProgram("?\n", cps);
12636 }
12637
12638 void
12639 TruncateGameEvent()
12640 {
12641     EditGameEvent();
12642     if (gameMode != EditGame) return;
12643     TruncateGame();
12644 }
12645
12646 void
12647 TruncateGame()
12648 {
12649     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12650     if (forwardMostMove > currentMove) {
12651         if (gameInfo.resultDetails != NULL) {
12652             free(gameInfo.resultDetails);
12653             gameInfo.resultDetails = NULL;
12654             gameInfo.result = GameUnfinished;
12655         }
12656         forwardMostMove = currentMove;
12657         HistorySet(parseList, backwardMostMove, forwardMostMove,
12658                    currentMove-1);
12659     }
12660 }
12661
12662 void
12663 HintEvent()
12664 {
12665     if (appData.noChessProgram) return;
12666     switch (gameMode) {
12667       case MachinePlaysWhite:
12668         if (WhiteOnMove(forwardMostMove)) {
12669             DisplayError(_("Wait until your turn"), 0);
12670             return;
12671         }
12672         break;
12673       case BeginningOfGame:
12674       case MachinePlaysBlack:
12675         if (!WhiteOnMove(forwardMostMove)) {
12676             DisplayError(_("Wait until your turn"), 0);
12677             return;
12678         }
12679         break;
12680       default:
12681         DisplayError(_("No hint available"), 0);
12682         return;
12683     }
12684     SendToProgram("hint\n", &first);
12685     hintRequested = TRUE;
12686 }
12687
12688 void
12689 BookEvent()
12690 {
12691     if (appData.noChessProgram) return;
12692     switch (gameMode) {
12693       case MachinePlaysWhite:
12694         if (WhiteOnMove(forwardMostMove)) {
12695             DisplayError(_("Wait until your turn"), 0);
12696             return;
12697         }
12698         break;
12699       case BeginningOfGame:
12700       case MachinePlaysBlack:
12701         if (!WhiteOnMove(forwardMostMove)) {
12702             DisplayError(_("Wait until your turn"), 0);
12703             return;
12704         }
12705         break;
12706       case EditPosition:
12707         EditPositionDone(TRUE);
12708         break;
12709       case TwoMachinesPlay:
12710         return;
12711       default:
12712         break;
12713     }
12714     SendToProgram("bk\n", &first);
12715     bookOutput[0] = NULLCHAR;
12716     bookRequested = TRUE;
12717 }
12718
12719 void
12720 AboutGameEvent()
12721 {
12722     char *tags = PGNTags(&gameInfo);
12723     TagsPopUp(tags, CmailMsg());
12724     free(tags);
12725 }
12726
12727 /* end button procedures */
12728
12729 void
12730 PrintPosition(fp, move)
12731      FILE *fp;
12732      int move;
12733 {
12734     int i, j;
12735     
12736     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12737         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12738             char c = PieceToChar(boards[move][i][j]);
12739             fputc(c == 'x' ? '.' : c, fp);
12740             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12741         }
12742     }
12743     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12744       fprintf(fp, "white to play\n");
12745     else
12746       fprintf(fp, "black to play\n");
12747 }
12748
12749 void
12750 PrintOpponents(fp)
12751      FILE *fp;
12752 {
12753     if (gameInfo.white != NULL) {
12754         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12755     } else {
12756         fprintf(fp, "\n");
12757     }
12758 }
12759
12760 /* Find last component of program's own name, using some heuristics */
12761 void
12762 TidyProgramName(prog, host, buf)
12763      char *prog, *host, buf[MSG_SIZ];
12764 {
12765     char *p, *q;
12766     int local = (strcmp(host, "localhost") == 0);
12767     while (!local && (p = strchr(prog, ';')) != NULL) {
12768         p++;
12769         while (*p == ' ') p++;
12770         prog = p;
12771     }
12772     if (*prog == '"' || *prog == '\'') {
12773         q = strchr(prog + 1, *prog);
12774     } else {
12775         q = strchr(prog, ' ');
12776     }
12777     if (q == NULL) q = prog + strlen(prog);
12778     p = q;
12779     while (p >= prog && *p != '/' && *p != '\\') p--;
12780     p++;
12781     if(p == prog && *p == '"') p++;
12782     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12783     memcpy(buf, p, q - p);
12784     buf[q - p] = NULLCHAR;
12785     if (!local) {
12786         strcat(buf, "@");
12787         strcat(buf, host);
12788     }
12789 }
12790
12791 char *
12792 TimeControlTagValue()
12793 {
12794     char buf[MSG_SIZ];
12795     if (!appData.clockMode) {
12796         strcpy(buf, "-");
12797     } else if (movesPerSession > 0) {
12798         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12799     } else if (timeIncrement == 0) {
12800         sprintf(buf, "%ld", timeControl/1000);
12801     } else {
12802         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12803     }
12804     return StrSave(buf);
12805 }
12806
12807 void
12808 SetGameInfo()
12809 {
12810     /* This routine is used only for certain modes */
12811     VariantClass v = gameInfo.variant;
12812     ChessMove r = GameUnfinished;
12813     char *p = NULL;
12814
12815     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12816         r = gameInfo.result; 
12817         p = gameInfo.resultDetails; 
12818         gameInfo.resultDetails = NULL;
12819     }
12820     ClearGameInfo(&gameInfo);
12821     gameInfo.variant = v;
12822
12823     switch (gameMode) {
12824       case MachinePlaysWhite:
12825         gameInfo.event = StrSave( appData.pgnEventHeader );
12826         gameInfo.site = StrSave(HostName());
12827         gameInfo.date = PGNDate();
12828         gameInfo.round = StrSave("-");
12829         gameInfo.white = StrSave(first.tidy);
12830         gameInfo.black = StrSave(UserName());
12831         gameInfo.timeControl = TimeControlTagValue();
12832         break;
12833
12834       case MachinePlaysBlack:
12835         gameInfo.event = StrSave( appData.pgnEventHeader );
12836         gameInfo.site = StrSave(HostName());
12837         gameInfo.date = PGNDate();
12838         gameInfo.round = StrSave("-");
12839         gameInfo.white = StrSave(UserName());
12840         gameInfo.black = StrSave(first.tidy);
12841         gameInfo.timeControl = TimeControlTagValue();
12842         break;
12843
12844       case TwoMachinesPlay:
12845         gameInfo.event = StrSave( appData.pgnEventHeader );
12846         gameInfo.site = StrSave(HostName());
12847         gameInfo.date = PGNDate();
12848         if (matchGame > 0) {
12849             char buf[MSG_SIZ];
12850             sprintf(buf, "%d", matchGame);
12851             gameInfo.round = StrSave(buf);
12852         } else {
12853             gameInfo.round = StrSave("-");
12854         }
12855         if (first.twoMachinesColor[0] == 'w') {
12856             gameInfo.white = StrSave(first.tidy);
12857             gameInfo.black = StrSave(second.tidy);
12858         } else {
12859             gameInfo.white = StrSave(second.tidy);
12860             gameInfo.black = StrSave(first.tidy);
12861         }
12862         gameInfo.timeControl = TimeControlTagValue();
12863         break;
12864
12865       case EditGame:
12866         gameInfo.event = StrSave("Edited game");
12867         gameInfo.site = StrSave(HostName());
12868         gameInfo.date = PGNDate();
12869         gameInfo.round = StrSave("-");
12870         gameInfo.white = StrSave("-");
12871         gameInfo.black = StrSave("-");
12872         gameInfo.result = r;
12873         gameInfo.resultDetails = p;
12874         break;
12875
12876       case EditPosition:
12877         gameInfo.event = StrSave("Edited position");
12878         gameInfo.site = StrSave(HostName());
12879         gameInfo.date = PGNDate();
12880         gameInfo.round = StrSave("-");
12881         gameInfo.white = StrSave("-");
12882         gameInfo.black = StrSave("-");
12883         break;
12884
12885       case IcsPlayingWhite:
12886       case IcsPlayingBlack:
12887       case IcsObserving:
12888       case IcsExamining:
12889         break;
12890
12891       case PlayFromGameFile:
12892         gameInfo.event = StrSave("Game from non-PGN file");
12893         gameInfo.site = StrSave(HostName());
12894         gameInfo.date = PGNDate();
12895         gameInfo.round = StrSave("-");
12896         gameInfo.white = StrSave("?");
12897         gameInfo.black = StrSave("?");
12898         break;
12899
12900       default:
12901         break;
12902     }
12903 }
12904
12905 void
12906 ReplaceComment(index, text)
12907      int index;
12908      char *text;
12909 {
12910     int len;
12911
12912     while (*text == '\n') text++;
12913     len = strlen(text);
12914     while (len > 0 && text[len - 1] == '\n') len--;
12915
12916     if (commentList[index] != NULL)
12917       free(commentList[index]);
12918
12919     if (len == 0) {
12920         commentList[index] = NULL;
12921         return;
12922     }
12923   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12924       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12925       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12926     commentList[index] = (char *) malloc(len + 2);
12927     strncpy(commentList[index], text, len);
12928     commentList[index][len] = '\n';
12929     commentList[index][len + 1] = NULLCHAR;
12930   } else { 
12931     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12932     char *p;
12933     commentList[index] = (char *) malloc(len + 6);
12934     strcpy(commentList[index], "{\n");
12935     strncpy(commentList[index]+2, text, len);
12936     commentList[index][len+2] = NULLCHAR;
12937     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12938     strcat(commentList[index], "\n}\n");
12939   }
12940 }
12941
12942 void
12943 CrushCRs(text)
12944      char *text;
12945 {
12946   char *p = text;
12947   char *q = text;
12948   char ch;
12949
12950   do {
12951     ch = *p++;
12952     if (ch == '\r') continue;
12953     *q++ = ch;
12954   } while (ch != '\0');
12955 }
12956
12957 void
12958 AppendComment(index, text, addBraces)
12959      int index;
12960      char *text;
12961      Boolean addBraces; // [HGM] braces: tells if we should add {}
12962 {
12963     int oldlen, len;
12964     char *old;
12965
12966 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12967     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12968
12969     CrushCRs(text);
12970     while (*text == '\n') text++;
12971     len = strlen(text);
12972     while (len > 0 && text[len - 1] == '\n') len--;
12973
12974     if (len == 0) return;
12975
12976     if (commentList[index] != NULL) {
12977         old = commentList[index];
12978         oldlen = strlen(old);
12979         while(commentList[index][oldlen-1] ==  '\n')
12980           commentList[index][--oldlen] = NULLCHAR;
12981         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12982         strcpy(commentList[index], old);
12983         free(old);
12984         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12985         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12986           if(addBraces) addBraces = FALSE; else { text++; len--; }
12987           while (*text == '\n') { text++; len--; }
12988           commentList[index][--oldlen] = NULLCHAR;
12989       }
12990         if(addBraces) strcat(commentList[index], "\n{\n");
12991         else          strcat(commentList[index], "\n");
12992         strcat(commentList[index], text);
12993         if(addBraces) strcat(commentList[index], "\n}\n");
12994         else          strcat(commentList[index], "\n");
12995     } else {
12996         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12997         if(addBraces)
12998              strcpy(commentList[index], "{\n");
12999         else commentList[index][0] = NULLCHAR;
13000         strcat(commentList[index], text);
13001         strcat(commentList[index], "\n");
13002         if(addBraces) strcat(commentList[index], "}\n");
13003     }
13004 }
13005
13006 static char * FindStr( char * text, char * sub_text )
13007 {
13008     char * result = strstr( text, sub_text );
13009
13010     if( result != NULL ) {
13011         result += strlen( sub_text );
13012     }
13013
13014     return result;
13015 }
13016
13017 /* [AS] Try to extract PV info from PGN comment */
13018 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13019 char *GetInfoFromComment( int index, char * text )
13020 {
13021     char * sep = text;
13022
13023     if( text != NULL && index > 0 ) {
13024         int score = 0;
13025         int depth = 0;
13026         int time = -1, sec = 0, deci;
13027         char * s_eval = FindStr( text, "[%eval " );
13028         char * s_emt = FindStr( text, "[%emt " );
13029
13030         if( s_eval != NULL || s_emt != NULL ) {
13031             /* New style */
13032             char delim;
13033
13034             if( s_eval != NULL ) {
13035                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13036                     return text;
13037                 }
13038
13039                 if( delim != ']' ) {
13040                     return text;
13041                 }
13042             }
13043
13044             if( s_emt != NULL ) {
13045             }
13046                 return text;
13047         }
13048         else {
13049             /* We expect something like: [+|-]nnn.nn/dd */
13050             int score_lo = 0;
13051
13052             if(*text != '{') return text; // [HGM] braces: must be normal comment
13053
13054             sep = strchr( text, '/' );
13055             if( sep == NULL || sep < (text+4) ) {
13056                 return text;
13057             }
13058
13059             time = -1; sec = -1; deci = -1;
13060             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13061                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13062                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13063                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13064                 return text;
13065             }
13066
13067             if( score_lo < 0 || score_lo >= 100 ) {
13068                 return text;
13069             }
13070
13071             if(sec >= 0) time = 600*time + 10*sec; else
13072             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13073
13074             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13075
13076             /* [HGM] PV time: now locate end of PV info */
13077             while( *++sep >= '0' && *sep <= '9'); // strip depth
13078             if(time >= 0)
13079             while( *++sep >= '0' && *sep <= '9'); // strip time
13080             if(sec >= 0)
13081             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13082             if(deci >= 0)
13083             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13084             while(*sep == ' ') sep++;
13085         }
13086
13087         if( depth <= 0 ) {
13088             return text;
13089         }
13090
13091         if( time < 0 ) {
13092             time = -1;
13093         }
13094
13095         pvInfoList[index-1].depth = depth;
13096         pvInfoList[index-1].score = score;
13097         pvInfoList[index-1].time  = 10*time; // centi-sec
13098         if(*sep == '}') *sep = 0; else *--sep = '{';
13099     }
13100     return sep;
13101 }
13102
13103 void
13104 SendToProgram(message, cps)
13105      char *message;
13106      ChessProgramState *cps;
13107 {
13108     int count, outCount, error;
13109     char buf[MSG_SIZ];
13110
13111     if (cps->pr == NULL) return;
13112     Attention(cps);
13113     
13114     if (appData.debugMode) {
13115         TimeMark now;
13116         GetTimeMark(&now);
13117         fprintf(debugFP, "%ld >%-6s: %s", 
13118                 SubtractTimeMarks(&now, &programStartTime),
13119                 cps->which, message);
13120     }
13121     
13122     count = strlen(message);
13123     outCount = OutputToProcess(cps->pr, message, count, &error);
13124     if (outCount < count && !exiting 
13125                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13126         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13127         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13128             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13129                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13130                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13131             } else {
13132                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13133             }
13134             gameInfo.resultDetails = StrSave(buf);
13135         }
13136         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13137     }
13138 }
13139
13140 void
13141 ReceiveFromProgram(isr, closure, message, count, error)
13142      InputSourceRef isr;
13143      VOIDSTAR closure;
13144      char *message;
13145      int count;
13146      int error;
13147 {
13148     char *end_str;
13149     char buf[MSG_SIZ];
13150     ChessProgramState *cps = (ChessProgramState *)closure;
13151
13152     if (isr != cps->isr) return; /* Killed intentionally */
13153     if (count <= 0) {
13154         if (count == 0) {
13155             sprintf(buf,
13156                     _("Error: %s chess program (%s) exited unexpectedly"),
13157                     cps->which, cps->program);
13158         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13159                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13160                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13161                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13162                 } else {
13163                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13164                 }
13165                 gameInfo.resultDetails = StrSave(buf);
13166             }
13167             RemoveInputSource(cps->isr);
13168             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13169         } else {
13170             sprintf(buf,
13171                     _("Error reading from %s chess program (%s)"),
13172                     cps->which, cps->program);
13173             RemoveInputSource(cps->isr);
13174
13175             /* [AS] Program is misbehaving badly... kill it */
13176             if( count == -2 ) {
13177                 DestroyChildProcess( cps->pr, 9 );
13178                 cps->pr = NoProc;
13179             }
13180
13181             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13182         }
13183         return;
13184     }
13185     
13186     if ((end_str = strchr(message, '\r')) != NULL)
13187       *end_str = NULLCHAR;
13188     if ((end_str = strchr(message, '\n')) != NULL)
13189       *end_str = NULLCHAR;
13190     
13191     if (appData.debugMode) {
13192         TimeMark now; int print = 1;
13193         char *quote = ""; char c; int i;
13194
13195         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13196                 char start = message[0];
13197                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13198                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13199                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13200                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13201                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13202                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13203                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13204                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13205                         { quote = "# "; print = (appData.engineComments == 2); }
13206                 message[0] = start; // restore original message
13207         }
13208         if(print) {
13209                 GetTimeMark(&now);
13210                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13211                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13212                         quote,
13213                         message);
13214         }
13215     }
13216
13217     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13218     if (appData.icsEngineAnalyze) {
13219         if (strstr(message, "whisper") != NULL ||
13220              strstr(message, "kibitz") != NULL || 
13221             strstr(message, "tellics") != NULL) return;
13222     }
13223
13224     HandleMachineMove(message, cps);
13225 }
13226
13227
13228 void
13229 SendTimeControl(cps, mps, tc, inc, sd, st)
13230      ChessProgramState *cps;
13231      int mps, inc, sd, st;
13232      long tc;
13233 {
13234     char buf[MSG_SIZ];
13235     int seconds;
13236
13237     if( timeControl_2 > 0 ) {
13238         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13239             tc = timeControl_2;
13240         }
13241     }
13242     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13243     inc /= cps->timeOdds;
13244     st  /= cps->timeOdds;
13245
13246     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13247
13248     if (st > 0) {
13249       /* Set exact time per move, normally using st command */
13250       if (cps->stKludge) {
13251         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13252         seconds = st % 60;
13253         if (seconds == 0) {
13254           sprintf(buf, "level 1 %d\n", st/60);
13255         } else {
13256           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13257         }
13258       } else {
13259         sprintf(buf, "st %d\n", st);
13260       }
13261     } else {
13262       /* Set conventional or incremental time control, using level command */
13263       if (seconds == 0) {
13264         /* Note old gnuchess bug -- minutes:seconds used to not work.
13265            Fixed in later versions, but still avoid :seconds
13266            when seconds is 0. */
13267         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13268       } else {
13269         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13270                 seconds, inc/1000);
13271       }
13272     }
13273     SendToProgram(buf, cps);
13274
13275     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13276     /* Orthogonally, limit search to given depth */
13277     if (sd > 0) {
13278       if (cps->sdKludge) {
13279         sprintf(buf, "depth\n%d\n", sd);
13280       } else {
13281         sprintf(buf, "sd %d\n", sd);
13282       }
13283       SendToProgram(buf, cps);
13284     }
13285
13286     if(cps->nps > 0) { /* [HGM] nps */
13287         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13288         else {
13289                 sprintf(buf, "nps %d\n", cps->nps);
13290               SendToProgram(buf, cps);
13291         }
13292     }
13293 }
13294
13295 ChessProgramState *WhitePlayer()
13296 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13297 {
13298     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13299        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13300         return &second;
13301     return &first;
13302 }
13303
13304 void
13305 SendTimeRemaining(cps, machineWhite)
13306      ChessProgramState *cps;
13307      int /*boolean*/ machineWhite;
13308 {
13309     char message[MSG_SIZ];
13310     long time, otime;
13311
13312     /* Note: this routine must be called when the clocks are stopped
13313        or when they have *just* been set or switched; otherwise
13314        it will be off by the time since the current tick started.
13315     */
13316     if (machineWhite) {
13317         time = whiteTimeRemaining / 10;
13318         otime = blackTimeRemaining / 10;
13319     } else {
13320         time = blackTimeRemaining / 10;
13321         otime = whiteTimeRemaining / 10;
13322     }
13323     /* [HGM] translate opponent's time by time-odds factor */
13324     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13325     if (appData.debugMode) {
13326         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13327     }
13328
13329     if (time <= 0) time = 1;
13330     if (otime <= 0) otime = 1;
13331     
13332     sprintf(message, "time %ld\n", time);
13333     SendToProgram(message, cps);
13334
13335     sprintf(message, "otim %ld\n", otime);
13336     SendToProgram(message, cps);
13337 }
13338
13339 int
13340 BoolFeature(p, name, loc, cps)
13341      char **p;
13342      char *name;
13343      int *loc;
13344      ChessProgramState *cps;
13345 {
13346   char buf[MSG_SIZ];
13347   int len = strlen(name);
13348   int val;
13349   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13350     (*p) += len + 1;
13351     sscanf(*p, "%d", &val);
13352     *loc = (val != 0);
13353     while (**p && **p != ' ') (*p)++;
13354     sprintf(buf, "accepted %s\n", name);
13355     SendToProgram(buf, cps);
13356     return TRUE;
13357   }
13358   return FALSE;
13359 }
13360
13361 int
13362 IntFeature(p, name, loc, cps)
13363      char **p;
13364      char *name;
13365      int *loc;
13366      ChessProgramState *cps;
13367 {
13368   char buf[MSG_SIZ];
13369   int len = strlen(name);
13370   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13371     (*p) += len + 1;
13372     sscanf(*p, "%d", loc);
13373     while (**p && **p != ' ') (*p)++;
13374     sprintf(buf, "accepted %s\n", name);
13375     SendToProgram(buf, cps);
13376     return TRUE;
13377   }
13378   return FALSE;
13379 }
13380
13381 int
13382 StringFeature(p, name, loc, cps)
13383      char **p;
13384      char *name;
13385      char loc[];
13386      ChessProgramState *cps;
13387 {
13388   char buf[MSG_SIZ];
13389   int len = strlen(name);
13390   if (strncmp((*p), name, len) == 0
13391       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13392     (*p) += len + 2;
13393     sscanf(*p, "%[^\"]", loc);
13394     while (**p && **p != '\"') (*p)++;
13395     if (**p == '\"') (*p)++;
13396     sprintf(buf, "accepted %s\n", name);
13397     SendToProgram(buf, cps);
13398     return TRUE;
13399   }
13400   return FALSE;
13401 }
13402
13403 int 
13404 ParseOption(Option *opt, ChessProgramState *cps)
13405 // [HGM] options: process the string that defines an engine option, and determine
13406 // name, type, default value, and allowed value range
13407 {
13408         char *p, *q, buf[MSG_SIZ];
13409         int n, min = (-1)<<31, max = 1<<31, def;
13410
13411         if(p = strstr(opt->name, " -spin ")) {
13412             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13413             if(max < min) max = min; // enforce consistency
13414             if(def < min) def = min;
13415             if(def > max) def = max;
13416             opt->value = def;
13417             opt->min = min;
13418             opt->max = max;
13419             opt->type = Spin;
13420         } else if((p = strstr(opt->name, " -slider "))) {
13421             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13422             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13423             if(max < min) max = min; // enforce consistency
13424             if(def < min) def = min;
13425             if(def > max) def = max;
13426             opt->value = def;
13427             opt->min = min;
13428             opt->max = max;
13429             opt->type = Spin; // Slider;
13430         } else if((p = strstr(opt->name, " -string "))) {
13431             opt->textValue = p+9;
13432             opt->type = TextBox;
13433         } else if((p = strstr(opt->name, " -file "))) {
13434             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13435             opt->textValue = p+7;
13436             opt->type = TextBox; // FileName;
13437         } else if((p = strstr(opt->name, " -path "))) {
13438             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13439             opt->textValue = p+7;
13440             opt->type = TextBox; // PathName;
13441         } else if(p = strstr(opt->name, " -check ")) {
13442             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13443             opt->value = (def != 0);
13444             opt->type = CheckBox;
13445         } else if(p = strstr(opt->name, " -combo ")) {
13446             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13447             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13448             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13449             opt->value = n = 0;
13450             while(q = StrStr(q, " /// ")) {
13451                 n++; *q = 0;    // count choices, and null-terminate each of them
13452                 q += 5;
13453                 if(*q == '*') { // remember default, which is marked with * prefix
13454                     q++;
13455                     opt->value = n;
13456                 }
13457                 cps->comboList[cps->comboCnt++] = q;
13458             }
13459             cps->comboList[cps->comboCnt++] = NULL;
13460             opt->max = n + 1;
13461             opt->type = ComboBox;
13462         } else if(p = strstr(opt->name, " -button")) {
13463             opt->type = Button;
13464         } else if(p = strstr(opt->name, " -save")) {
13465             opt->type = SaveButton;
13466         } else return FALSE;
13467         *p = 0; // terminate option name
13468         // now look if the command-line options define a setting for this engine option.
13469         if(cps->optionSettings && cps->optionSettings[0])
13470             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13471         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13472                 sprintf(buf, "option %s", p);
13473                 if(p = strstr(buf, ",")) *p = 0;
13474                 strcat(buf, "\n");
13475                 SendToProgram(buf, cps);
13476         }
13477         return TRUE;
13478 }
13479
13480 void
13481 FeatureDone(cps, val)
13482      ChessProgramState* cps;
13483      int val;
13484 {
13485   DelayedEventCallback cb = GetDelayedEvent();
13486   if ((cb == InitBackEnd3 && cps == &first) ||
13487       (cb == TwoMachinesEventIfReady && cps == &second)) {
13488     CancelDelayedEvent();
13489     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13490   }
13491   cps->initDone = val;
13492 }
13493
13494 /* Parse feature command from engine */
13495 void
13496 ParseFeatures(args, cps)
13497      char* args;
13498      ChessProgramState *cps;  
13499 {
13500   char *p = args;
13501   char *q;
13502   int val;
13503   char buf[MSG_SIZ];
13504
13505   for (;;) {
13506     while (*p == ' ') p++;
13507     if (*p == NULLCHAR) return;
13508
13509     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13510     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13511     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13512     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13513     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13514     if (BoolFeature(&p, "reuse", &val, cps)) {
13515       /* Engine can disable reuse, but can't enable it if user said no */
13516       if (!val) cps->reuse = FALSE;
13517       continue;
13518     }
13519     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13520     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13521       if (gameMode == TwoMachinesPlay) {
13522         DisplayTwoMachinesTitle();
13523       } else {
13524         DisplayTitle("");
13525       }
13526       continue;
13527     }
13528     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13529     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13530     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13531     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13532     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13533     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13534     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13535     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13536     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13537     if (IntFeature(&p, "done", &val, cps)) {
13538       FeatureDone(cps, val);
13539       continue;
13540     }
13541     /* Added by Tord: */
13542     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13543     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13544     /* End of additions by Tord */
13545
13546     /* [HGM] added features: */
13547     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13548     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13549     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13550     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13551     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13552     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13553     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13554         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13555             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13556             SendToProgram(buf, cps);
13557             continue;
13558         }
13559         if(cps->nrOptions >= MAX_OPTIONS) {
13560             cps->nrOptions--;
13561             sprintf(buf, "%s engine has too many options\n", cps->which);
13562             DisplayError(buf, 0);
13563         }
13564         continue;
13565     }
13566     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13567     /* End of additions by HGM */
13568
13569     /* unknown feature: complain and skip */
13570     q = p;
13571     while (*q && *q != '=') q++;
13572     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13573     SendToProgram(buf, cps);
13574     p = q;
13575     if (*p == '=') {
13576       p++;
13577       if (*p == '\"') {
13578         p++;
13579         while (*p && *p != '\"') p++;
13580         if (*p == '\"') p++;
13581       } else {
13582         while (*p && *p != ' ') p++;
13583       }
13584     }
13585   }
13586
13587 }
13588
13589 void
13590 PeriodicUpdatesEvent(newState)
13591      int newState;
13592 {
13593     if (newState == appData.periodicUpdates)
13594       return;
13595
13596     appData.periodicUpdates=newState;
13597
13598     /* Display type changes, so update it now */
13599 //    DisplayAnalysis();
13600
13601     /* Get the ball rolling again... */
13602     if (newState) {
13603         AnalysisPeriodicEvent(1);
13604         StartAnalysisClock();
13605     }
13606 }
13607
13608 void
13609 PonderNextMoveEvent(newState)
13610      int newState;
13611 {
13612     if (newState == appData.ponderNextMove) return;
13613     if (gameMode == EditPosition) EditPositionDone(TRUE);
13614     if (newState) {
13615         SendToProgram("hard\n", &first);
13616         if (gameMode == TwoMachinesPlay) {
13617             SendToProgram("hard\n", &second);
13618         }
13619     } else {
13620         SendToProgram("easy\n", &first);
13621         thinkOutput[0] = NULLCHAR;
13622         if (gameMode == TwoMachinesPlay) {
13623             SendToProgram("easy\n", &second);
13624         }
13625     }
13626     appData.ponderNextMove = newState;
13627 }
13628
13629 void
13630 NewSettingEvent(option, command, value)
13631      char *command;
13632      int option, value;
13633 {
13634     char buf[MSG_SIZ];
13635
13636     if (gameMode == EditPosition) EditPositionDone(TRUE);
13637     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13638     SendToProgram(buf, &first);
13639     if (gameMode == TwoMachinesPlay) {
13640         SendToProgram(buf, &second);
13641     }
13642 }
13643
13644 void
13645 ShowThinkingEvent()
13646 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13647 {
13648     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13649     int newState = appData.showThinking
13650         // [HGM] thinking: other features now need thinking output as well
13651         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13652     
13653     if (oldState == newState) return;
13654     oldState = newState;
13655     if (gameMode == EditPosition) EditPositionDone(TRUE);
13656     if (oldState) {
13657         SendToProgram("post\n", &first);
13658         if (gameMode == TwoMachinesPlay) {
13659             SendToProgram("post\n", &second);
13660         }
13661     } else {
13662         SendToProgram("nopost\n", &first);
13663         thinkOutput[0] = NULLCHAR;
13664         if (gameMode == TwoMachinesPlay) {
13665             SendToProgram("nopost\n", &second);
13666         }
13667     }
13668 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13669 }
13670
13671 void
13672 AskQuestionEvent(title, question, replyPrefix, which)
13673      char *title; char *question; char *replyPrefix; char *which;
13674 {
13675   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13676   if (pr == NoProc) return;
13677   AskQuestion(title, question, replyPrefix, pr);
13678 }
13679
13680 void
13681 DisplayMove(moveNumber)
13682      int moveNumber;
13683 {
13684     char message[MSG_SIZ];
13685     char res[MSG_SIZ];
13686     char cpThinkOutput[MSG_SIZ];
13687
13688     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13689     
13690     if (moveNumber == forwardMostMove - 1 || 
13691         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13692
13693         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13694
13695         if (strchr(cpThinkOutput, '\n')) {
13696             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13697         }
13698     } else {
13699         *cpThinkOutput = NULLCHAR;
13700     }
13701
13702     /* [AS] Hide thinking from human user */
13703     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13704         *cpThinkOutput = NULLCHAR;
13705         if( thinkOutput[0] != NULLCHAR ) {
13706             int i;
13707
13708             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13709                 cpThinkOutput[i] = '.';
13710             }
13711             cpThinkOutput[i] = NULLCHAR;
13712             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13713         }
13714     }
13715
13716     if (moveNumber == forwardMostMove - 1 &&
13717         gameInfo.resultDetails != NULL) {
13718         if (gameInfo.resultDetails[0] == NULLCHAR) {
13719             sprintf(res, " %s", PGNResult(gameInfo.result));
13720         } else {
13721             sprintf(res, " {%s} %s",
13722                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13723         }
13724     } else {
13725         res[0] = NULLCHAR;
13726     }
13727
13728     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13729         DisplayMessage(res, cpThinkOutput);
13730     } else {
13731         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13732                 WhiteOnMove(moveNumber) ? " " : ".. ",
13733                 parseList[moveNumber], res);
13734         DisplayMessage(message, cpThinkOutput);
13735     }
13736 }
13737
13738 void
13739 DisplayComment(moveNumber, text)
13740      int moveNumber;
13741      char *text;
13742 {
13743     char title[MSG_SIZ];
13744     char buf[8000]; // comment can be long!
13745     int score, depth;
13746     
13747     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13748       strcpy(title, "Comment");
13749     } else {
13750       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13751               WhiteOnMove(moveNumber) ? " " : ".. ",
13752               parseList[moveNumber]);
13753     }
13754     // [HGM] PV info: display PV info together with (or as) comment
13755     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13756       if(text == NULL) text = "";                                           
13757       score = pvInfoList[moveNumber].score;
13758       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13759               depth, (pvInfoList[moveNumber].time+50)/100, text);
13760       text = buf;
13761     }
13762     if (text != NULL && (appData.autoDisplayComment || commentUp))
13763         CommentPopUp(title, text);
13764 }
13765
13766 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13767  * might be busy thinking or pondering.  It can be omitted if your
13768  * gnuchess is configured to stop thinking immediately on any user
13769  * input.  However, that gnuchess feature depends on the FIONREAD
13770  * ioctl, which does not work properly on some flavors of Unix.
13771  */
13772 void
13773 Attention(cps)
13774      ChessProgramState *cps;
13775 {
13776 #if ATTENTION
13777     if (!cps->useSigint) return;
13778     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13779     switch (gameMode) {
13780       case MachinePlaysWhite:
13781       case MachinePlaysBlack:
13782       case TwoMachinesPlay:
13783       case IcsPlayingWhite:
13784       case IcsPlayingBlack:
13785       case AnalyzeMode:
13786       case AnalyzeFile:
13787         /* Skip if we know it isn't thinking */
13788         if (!cps->maybeThinking) return;
13789         if (appData.debugMode)
13790           fprintf(debugFP, "Interrupting %s\n", cps->which);
13791         InterruptChildProcess(cps->pr);
13792         cps->maybeThinking = FALSE;
13793         break;
13794       default:
13795         break;
13796     }
13797 #endif /*ATTENTION*/
13798 }
13799
13800 int
13801 CheckFlags()
13802 {
13803     if (whiteTimeRemaining <= 0) {
13804         if (!whiteFlag) {
13805             whiteFlag = TRUE;
13806             if (appData.icsActive) {
13807                 if (appData.autoCallFlag &&
13808                     gameMode == IcsPlayingBlack && !blackFlag) {
13809                   SendToICS(ics_prefix);
13810                   SendToICS("flag\n");
13811                 }
13812             } else {
13813                 if (blackFlag) {
13814                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13815                 } else {
13816                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13817                     if (appData.autoCallFlag) {
13818                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13819                         return TRUE;
13820                     }
13821                 }
13822             }
13823         }
13824     }
13825     if (blackTimeRemaining <= 0) {
13826         if (!blackFlag) {
13827             blackFlag = TRUE;
13828             if (appData.icsActive) {
13829                 if (appData.autoCallFlag &&
13830                     gameMode == IcsPlayingWhite && !whiteFlag) {
13831                   SendToICS(ics_prefix);
13832                   SendToICS("flag\n");
13833                 }
13834             } else {
13835                 if (whiteFlag) {
13836                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13837                 } else {
13838                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13839                     if (appData.autoCallFlag) {
13840                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13841                         return TRUE;
13842                     }
13843                 }
13844             }
13845         }
13846     }
13847     return FALSE;
13848 }
13849
13850 void
13851 CheckTimeControl()
13852 {
13853     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13854         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13855
13856     /*
13857      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13858      */
13859     if ( !WhiteOnMove(forwardMostMove) )
13860         /* White made time control */
13861         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13862         /* [HGM] time odds: correct new time quota for time odds! */
13863                                             / WhitePlayer()->timeOdds;
13864       else
13865         /* Black made time control */
13866         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13867                                             / WhitePlayer()->other->timeOdds;
13868 }
13869
13870 void
13871 DisplayBothClocks()
13872 {
13873     int wom = gameMode == EditPosition ?
13874       !blackPlaysFirst : WhiteOnMove(currentMove);
13875     DisplayWhiteClock(whiteTimeRemaining, wom);
13876     DisplayBlackClock(blackTimeRemaining, !wom);
13877 }
13878
13879
13880 /* Timekeeping seems to be a portability nightmare.  I think everyone
13881    has ftime(), but I'm really not sure, so I'm including some ifdefs
13882    to use other calls if you don't.  Clocks will be less accurate if
13883    you have neither ftime nor gettimeofday.
13884 */
13885
13886 /* VS 2008 requires the #include outside of the function */
13887 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13888 #include <sys/timeb.h>
13889 #endif
13890
13891 /* Get the current time as a TimeMark */
13892 void
13893 GetTimeMark(tm)
13894      TimeMark *tm;
13895 {
13896 #if HAVE_GETTIMEOFDAY
13897
13898     struct timeval timeVal;
13899     struct timezone timeZone;
13900
13901     gettimeofday(&timeVal, &timeZone);
13902     tm->sec = (long) timeVal.tv_sec; 
13903     tm->ms = (int) (timeVal.tv_usec / 1000L);
13904
13905 #else /*!HAVE_GETTIMEOFDAY*/
13906 #if HAVE_FTIME
13907
13908 // include <sys/timeb.h> / moved to just above start of function
13909     struct timeb timeB;
13910
13911     ftime(&timeB);
13912     tm->sec = (long) timeB.time;
13913     tm->ms = (int) timeB.millitm;
13914
13915 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13916     tm->sec = (long) time(NULL);
13917     tm->ms = 0;
13918 #endif
13919 #endif
13920 }
13921
13922 /* Return the difference in milliseconds between two
13923    time marks.  We assume the difference will fit in a long!
13924 */
13925 long
13926 SubtractTimeMarks(tm2, tm1)
13927      TimeMark *tm2, *tm1;
13928 {
13929     return 1000L*(tm2->sec - tm1->sec) +
13930            (long) (tm2->ms - tm1->ms);
13931 }
13932
13933
13934 /*
13935  * Code to manage the game clocks.
13936  *
13937  * In tournament play, black starts the clock and then white makes a move.
13938  * We give the human user a slight advantage if he is playing white---the
13939  * clocks don't run until he makes his first move, so it takes zero time.
13940  * Also, we don't account for network lag, so we could get out of sync
13941  * with GNU Chess's clock -- but then, referees are always right.  
13942  */
13943
13944 static TimeMark tickStartTM;
13945 static long intendedTickLength;
13946
13947 long
13948 NextTickLength(timeRemaining)
13949      long timeRemaining;
13950 {
13951     long nominalTickLength, nextTickLength;
13952
13953     if (timeRemaining > 0L && timeRemaining <= 10000L)
13954       nominalTickLength = 100L;
13955     else
13956       nominalTickLength = 1000L;
13957     nextTickLength = timeRemaining % nominalTickLength;
13958     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13959
13960     return nextTickLength;
13961 }
13962
13963 /* Adjust clock one minute up or down */
13964 void
13965 AdjustClock(Boolean which, int dir)
13966 {
13967     if(which) blackTimeRemaining += 60000*dir;
13968     else      whiteTimeRemaining += 60000*dir;
13969     DisplayBothClocks();
13970 }
13971
13972 /* Stop clocks and reset to a fresh time control */
13973 void
13974 ResetClocks() 
13975 {
13976     (void) StopClockTimer();
13977     if (appData.icsActive) {
13978         whiteTimeRemaining = blackTimeRemaining = 0;
13979     } else if (searchTime) {
13980         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13981         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13982     } else { /* [HGM] correct new time quote for time odds */
13983         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13984         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13985     }
13986     if (whiteFlag || blackFlag) {
13987         DisplayTitle("");
13988         whiteFlag = blackFlag = FALSE;
13989     }
13990     DisplayBothClocks();
13991 }
13992
13993 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13994
13995 /* Decrement running clock by amount of time that has passed */
13996 void
13997 DecrementClocks()
13998 {
13999     long timeRemaining;
14000     long lastTickLength, fudge;
14001     TimeMark now;
14002
14003     if (!appData.clockMode) return;
14004     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14005         
14006     GetTimeMark(&now);
14007
14008     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14009
14010     /* Fudge if we woke up a little too soon */
14011     fudge = intendedTickLength - lastTickLength;
14012     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14013
14014     if (WhiteOnMove(forwardMostMove)) {
14015         if(whiteNPS >= 0) lastTickLength = 0;
14016         timeRemaining = whiteTimeRemaining -= lastTickLength;
14017         DisplayWhiteClock(whiteTimeRemaining - fudge,
14018                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14019     } else {
14020         if(blackNPS >= 0) lastTickLength = 0;
14021         timeRemaining = blackTimeRemaining -= lastTickLength;
14022         DisplayBlackClock(blackTimeRemaining - fudge,
14023                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14024     }
14025
14026     if (CheckFlags()) return;
14027         
14028     tickStartTM = now;
14029     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14030     StartClockTimer(intendedTickLength);
14031
14032     /* if the time remaining has fallen below the alarm threshold, sound the
14033      * alarm. if the alarm has sounded and (due to a takeback or time control
14034      * with increment) the time remaining has increased to a level above the
14035      * threshold, reset the alarm so it can sound again. 
14036      */
14037     
14038     if (appData.icsActive && appData.icsAlarm) {
14039
14040         /* make sure we are dealing with the user's clock */
14041         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14042                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14043            )) return;
14044
14045         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14046             alarmSounded = FALSE;
14047         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14048             PlayAlarmSound();
14049             alarmSounded = TRUE;
14050         }
14051     }
14052 }
14053
14054
14055 /* A player has just moved, so stop the previously running
14056    clock and (if in clock mode) start the other one.
14057    We redisplay both clocks in case we're in ICS mode, because
14058    ICS gives us an update to both clocks after every move.
14059    Note that this routine is called *after* forwardMostMove
14060    is updated, so the last fractional tick must be subtracted
14061    from the color that is *not* on move now.
14062 */
14063 void
14064 SwitchClocks(int newMoveNr)
14065 {
14066     long lastTickLength;
14067     TimeMark now;
14068     int flagged = FALSE;
14069
14070     GetTimeMark(&now);
14071
14072     if (StopClockTimer() && appData.clockMode) {
14073         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14074         if (!WhiteOnMove(forwardMostMove)) {
14075             if(blackNPS >= 0) lastTickLength = 0;
14076             blackTimeRemaining -= lastTickLength;
14077            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14078 //         if(pvInfoList[forwardMostMove-1].time == -1)
14079                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14080                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14081         } else {
14082            if(whiteNPS >= 0) lastTickLength = 0;
14083            whiteTimeRemaining -= lastTickLength;
14084            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14085 //         if(pvInfoList[forwardMostMove-1].time == -1)
14086                  pvInfoList[forwardMostMove-1].time = 
14087                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14088         }
14089         flagged = CheckFlags();
14090     }
14091     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14092     CheckTimeControl();
14093
14094     if (flagged || !appData.clockMode) return;
14095
14096     switch (gameMode) {
14097       case MachinePlaysBlack:
14098       case MachinePlaysWhite:
14099       case BeginningOfGame:
14100         if (pausing) return;
14101         break;
14102
14103       case EditGame:
14104       case PlayFromGameFile:
14105       case IcsExamining:
14106         return;
14107
14108       default:
14109         break;
14110     }
14111
14112     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14113         if(WhiteOnMove(forwardMostMove))
14114              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14115         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14116     }
14117
14118     tickStartTM = now;
14119     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14120       whiteTimeRemaining : blackTimeRemaining);
14121     StartClockTimer(intendedTickLength);
14122 }
14123         
14124
14125 /* Stop both clocks */
14126 void
14127 StopClocks()
14128 {       
14129     long lastTickLength;
14130     TimeMark now;
14131
14132     if (!StopClockTimer()) return;
14133     if (!appData.clockMode) return;
14134
14135     GetTimeMark(&now);
14136
14137     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14138     if (WhiteOnMove(forwardMostMove)) {
14139         if(whiteNPS >= 0) lastTickLength = 0;
14140         whiteTimeRemaining -= lastTickLength;
14141         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14142     } else {
14143         if(blackNPS >= 0) lastTickLength = 0;
14144         blackTimeRemaining -= lastTickLength;
14145         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14146     }
14147     CheckFlags();
14148 }
14149         
14150 /* Start clock of player on move.  Time may have been reset, so
14151    if clock is already running, stop and restart it. */
14152 void
14153 StartClocks()
14154 {
14155     (void) StopClockTimer(); /* in case it was running already */
14156     DisplayBothClocks();
14157     if (CheckFlags()) return;
14158
14159     if (!appData.clockMode) return;
14160     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14161
14162     GetTimeMark(&tickStartTM);
14163     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14164       whiteTimeRemaining : blackTimeRemaining);
14165
14166    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14167     whiteNPS = blackNPS = -1; 
14168     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14169        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14170         whiteNPS = first.nps;
14171     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14172        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14173         blackNPS = first.nps;
14174     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14175         whiteNPS = second.nps;
14176     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14177         blackNPS = second.nps;
14178     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14179
14180     StartClockTimer(intendedTickLength);
14181 }
14182
14183 char *
14184 TimeString(ms)
14185      long ms;
14186 {
14187     long second, minute, hour, day;
14188     char *sign = "";
14189     static char buf[32];
14190     
14191     if (ms > 0 && ms <= 9900) {
14192       /* convert milliseconds to tenths, rounding up */
14193       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14194
14195       sprintf(buf, " %03.1f ", tenths/10.0);
14196       return buf;
14197     }
14198
14199     /* convert milliseconds to seconds, rounding up */
14200     /* use floating point to avoid strangeness of integer division
14201        with negative dividends on many machines */
14202     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14203
14204     if (second < 0) {
14205         sign = "-";
14206         second = -second;
14207     }
14208     
14209     day = second / (60 * 60 * 24);
14210     second = second % (60 * 60 * 24);
14211     hour = second / (60 * 60);
14212     second = second % (60 * 60);
14213     minute = second / 60;
14214     second = second % 60;
14215     
14216     if (day > 0)
14217       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14218               sign, day, hour, minute, second);
14219     else if (hour > 0)
14220       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14221     else
14222       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14223     
14224     return buf;
14225 }
14226
14227
14228 /*
14229  * This is necessary because some C libraries aren't ANSI C compliant yet.
14230  */
14231 char *
14232 StrStr(string, match)
14233      char *string, *match;
14234 {
14235     int i, length;
14236     
14237     length = strlen(match);
14238     
14239     for (i = strlen(string) - length; i >= 0; i--, string++)
14240       if (!strncmp(match, string, length))
14241         return string;
14242     
14243     return NULL;
14244 }
14245
14246 char *
14247 StrCaseStr(string, match)
14248      char *string, *match;
14249 {
14250     int i, j, length;
14251     
14252     length = strlen(match);
14253     
14254     for (i = strlen(string) - length; i >= 0; i--, string++) {
14255         for (j = 0; j < length; j++) {
14256             if (ToLower(match[j]) != ToLower(string[j]))
14257               break;
14258         }
14259         if (j == length) return string;
14260     }
14261
14262     return NULL;
14263 }
14264
14265 #ifndef _amigados
14266 int
14267 StrCaseCmp(s1, s2)
14268      char *s1, *s2;
14269 {
14270     char c1, c2;
14271     
14272     for (;;) {
14273         c1 = ToLower(*s1++);
14274         c2 = ToLower(*s2++);
14275         if (c1 > c2) return 1;
14276         if (c1 < c2) return -1;
14277         if (c1 == NULLCHAR) return 0;
14278     }
14279 }
14280
14281
14282 int
14283 ToLower(c)
14284      int c;
14285 {
14286     return isupper(c) ? tolower(c) : c;
14287 }
14288
14289
14290 int
14291 ToUpper(c)
14292      int c;
14293 {
14294     return islower(c) ? toupper(c) : c;
14295 }
14296 #endif /* !_amigados    */
14297
14298 char *
14299 StrSave(s)
14300      char *s;
14301 {
14302     char *ret;
14303
14304     if ((ret = (char *) malloc(strlen(s) + 1))) {
14305         strcpy(ret, s);
14306     }
14307     return ret;
14308 }
14309
14310 char *
14311 StrSavePtr(s, savePtr)
14312      char *s, **savePtr;
14313 {
14314     if (*savePtr) {
14315         free(*savePtr);
14316     }
14317     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14318         strcpy(*savePtr, s);
14319     }
14320     return(*savePtr);
14321 }
14322
14323 char *
14324 PGNDate()
14325 {
14326     time_t clock;
14327     struct tm *tm;
14328     char buf[MSG_SIZ];
14329
14330     clock = time((time_t *)NULL);
14331     tm = localtime(&clock);
14332     sprintf(buf, "%04d.%02d.%02d",
14333             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14334     return StrSave(buf);
14335 }
14336
14337
14338 char *
14339 PositionToFEN(move, overrideCastling)
14340      int move;
14341      char *overrideCastling;
14342 {
14343     int i, j, fromX, fromY, toX, toY;
14344     int whiteToPlay;
14345     char buf[128];
14346     char *p, *q;
14347     int emptycount;
14348     ChessSquare piece;
14349
14350     whiteToPlay = (gameMode == EditPosition) ?
14351       !blackPlaysFirst : (move % 2 == 0);
14352     p = buf;
14353
14354     /* Piece placement data */
14355     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14356         emptycount = 0;
14357         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14358             if (boards[move][i][j] == EmptySquare) {
14359                 emptycount++;
14360             } else { ChessSquare piece = boards[move][i][j];
14361                 if (emptycount > 0) {
14362                     if(emptycount<10) /* [HGM] can be >= 10 */
14363                         *p++ = '0' + emptycount;
14364                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14365                     emptycount = 0;
14366                 }
14367                 if(PieceToChar(piece) == '+') {
14368                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14369                     *p++ = '+';
14370                     piece = (ChessSquare)(DEMOTED piece);
14371                 } 
14372                 *p++ = PieceToChar(piece);
14373                 if(p[-1] == '~') {
14374                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14375                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14376                     *p++ = '~';
14377                 }
14378             }
14379         }
14380         if (emptycount > 0) {
14381             if(emptycount<10) /* [HGM] can be >= 10 */
14382                 *p++ = '0' + emptycount;
14383             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14384             emptycount = 0;
14385         }
14386         *p++ = '/';
14387     }
14388     *(p - 1) = ' ';
14389
14390     /* [HGM] print Crazyhouse or Shogi holdings */
14391     if( gameInfo.holdingsWidth ) {
14392         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14393         q = p;
14394         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14395             piece = boards[move][i][BOARD_WIDTH-1];
14396             if( piece != EmptySquare )
14397               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14398                   *p++ = PieceToChar(piece);
14399         }
14400         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14401             piece = boards[move][BOARD_HEIGHT-i-1][0];
14402             if( piece != EmptySquare )
14403               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14404                   *p++ = PieceToChar(piece);
14405         }
14406
14407         if( q == p ) *p++ = '-';
14408         *p++ = ']';
14409         *p++ = ' ';
14410     }
14411
14412     /* Active color */
14413     *p++ = whiteToPlay ? 'w' : 'b';
14414     *p++ = ' ';
14415
14416   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14417     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14418   } else {
14419   if(nrCastlingRights) {
14420      q = p;
14421      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14422        /* [HGM] write directly from rights */
14423            if(boards[move][CASTLING][2] != NoRights &&
14424               boards[move][CASTLING][0] != NoRights   )
14425                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14426            if(boards[move][CASTLING][2] != NoRights &&
14427               boards[move][CASTLING][1] != NoRights   )
14428                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14429            if(boards[move][CASTLING][5] != NoRights &&
14430               boards[move][CASTLING][3] != NoRights   )
14431                 *p++ = boards[move][CASTLING][3] + AAA;
14432            if(boards[move][CASTLING][5] != NoRights &&
14433               boards[move][CASTLING][4] != NoRights   )
14434                 *p++ = boards[move][CASTLING][4] + AAA;
14435      } else {
14436
14437         /* [HGM] write true castling rights */
14438         if( nrCastlingRights == 6 ) {
14439             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14440                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14441             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14442                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14443             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14444                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14445             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14446                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14447         }
14448      }
14449      if (q == p) *p++ = '-'; /* No castling rights */
14450      *p++ = ' ';
14451   }
14452
14453   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14454      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14455     /* En passant target square */
14456     if (move > backwardMostMove) {
14457         fromX = moveList[move - 1][0] - AAA;
14458         fromY = moveList[move - 1][1] - ONE;
14459         toX = moveList[move - 1][2] - AAA;
14460         toY = moveList[move - 1][3] - ONE;
14461         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14462             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14463             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14464             fromX == toX) {
14465             /* 2-square pawn move just happened */
14466             *p++ = toX + AAA;
14467             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14468         } else {
14469             *p++ = '-';
14470         }
14471     } else if(move == backwardMostMove) {
14472         // [HGM] perhaps we should always do it like this, and forget the above?
14473         if((signed char)boards[move][EP_STATUS] >= 0) {
14474             *p++ = boards[move][EP_STATUS] + AAA;
14475             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14476         } else {
14477             *p++ = '-';
14478         }
14479     } else {
14480         *p++ = '-';
14481     }
14482     *p++ = ' ';
14483   }
14484   }
14485
14486     /* [HGM] find reversible plies */
14487     {   int i = 0, j=move;
14488
14489         if (appData.debugMode) { int k;
14490             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14491             for(k=backwardMostMove; k<=forwardMostMove; k++)
14492                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14493
14494         }
14495
14496         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14497         if( j == backwardMostMove ) i += initialRulePlies;
14498         sprintf(p, "%d ", i);
14499         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14500     }
14501     /* Fullmove number */
14502     sprintf(p, "%d", (move / 2) + 1);
14503     
14504     return StrSave(buf);
14505 }
14506
14507 Boolean
14508 ParseFEN(board, blackPlaysFirst, fen)
14509     Board board;
14510      int *blackPlaysFirst;
14511      char *fen;
14512 {
14513     int i, j;
14514     char *p;
14515     int emptycount;
14516     ChessSquare piece;
14517
14518     p = fen;
14519
14520     /* [HGM] by default clear Crazyhouse holdings, if present */
14521     if(gameInfo.holdingsWidth) {
14522        for(i=0; i<BOARD_HEIGHT; i++) {
14523            board[i][0]             = EmptySquare; /* black holdings */
14524            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14525            board[i][1]             = (ChessSquare) 0; /* black counts */
14526            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14527        }
14528     }
14529
14530     /* Piece placement data */
14531     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14532         j = 0;
14533         for (;;) {
14534             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14535                 if (*p == '/') p++;
14536                 emptycount = gameInfo.boardWidth - j;
14537                 while (emptycount--)
14538                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14539                 break;
14540 #if(BOARD_FILES >= 10)
14541             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14542                 p++; emptycount=10;
14543                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14544                 while (emptycount--)
14545                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14546 #endif
14547             } else if (isdigit(*p)) {
14548                 emptycount = *p++ - '0';
14549                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14550                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14551                 while (emptycount--)
14552                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14553             } else if (*p == '+' || isalpha(*p)) {
14554                 if (j >= gameInfo.boardWidth) return FALSE;
14555                 if(*p=='+') {
14556                     piece = CharToPiece(*++p);
14557                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14558                     piece = (ChessSquare) (PROMOTED piece ); p++;
14559                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14560                 } else piece = CharToPiece(*p++);
14561
14562                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14563                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14564                     piece = (ChessSquare) (PROMOTED piece);
14565                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14566                     p++;
14567                 }
14568                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14569             } else {
14570                 return FALSE;
14571             }
14572         }
14573     }
14574     while (*p == '/' || *p == ' ') p++;
14575
14576     /* [HGM] look for Crazyhouse holdings here */
14577     while(*p==' ') p++;
14578     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14579         if(*p == '[') p++;
14580         if(*p == '-' ) *p++; /* empty holdings */ else {
14581             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14582             /* if we would allow FEN reading to set board size, we would   */
14583             /* have to add holdings and shift the board read so far here   */
14584             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14585                 *p++;
14586                 if((int) piece >= (int) BlackPawn ) {
14587                     i = (int)piece - (int)BlackPawn;
14588                     i = PieceToNumber((ChessSquare)i);
14589                     if( i >= gameInfo.holdingsSize ) return FALSE;
14590                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14591                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14592                 } else {
14593                     i = (int)piece - (int)WhitePawn;
14594                     i = PieceToNumber((ChessSquare)i);
14595                     if( i >= gameInfo.holdingsSize ) return FALSE;
14596                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14597                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14598                 }
14599             }
14600         }
14601         if(*p == ']') *p++;
14602     }
14603
14604     while(*p == ' ') p++;
14605
14606     /* Active color */
14607     switch (*p++) {
14608       case 'w':
14609         *blackPlaysFirst = FALSE;
14610         break;
14611       case 'b': 
14612         *blackPlaysFirst = TRUE;
14613         break;
14614       default:
14615         return FALSE;
14616     }
14617
14618     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14619     /* return the extra info in global variiables             */
14620
14621     /* set defaults in case FEN is incomplete */
14622     board[EP_STATUS] = EP_UNKNOWN;
14623     for(i=0; i<nrCastlingRights; i++ ) {
14624         board[CASTLING][i] =
14625             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14626     }   /* assume possible unless obviously impossible */
14627     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14628     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14629     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14630                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14631     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14632     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14633     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14634                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14635     FENrulePlies = 0;
14636
14637     while(*p==' ') p++;
14638     if(nrCastlingRights) {
14639       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14640           /* castling indicator present, so default becomes no castlings */
14641           for(i=0; i<nrCastlingRights; i++ ) {
14642                  board[CASTLING][i] = NoRights;
14643           }
14644       }
14645       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14646              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14647              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14648              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14649         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14650
14651         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14652             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14653             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14654         }
14655         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14656             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14657         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14658                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14659         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14660                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14661         switch(c) {
14662           case'K':
14663               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14664               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14665               board[CASTLING][2] = whiteKingFile;
14666               break;
14667           case'Q':
14668               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14669               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14670               board[CASTLING][2] = whiteKingFile;
14671               break;
14672           case'k':
14673               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14674               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14675               board[CASTLING][5] = blackKingFile;
14676               break;
14677           case'q':
14678               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14679               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14680               board[CASTLING][5] = blackKingFile;
14681           case '-':
14682               break;
14683           default: /* FRC castlings */
14684               if(c >= 'a') { /* black rights */
14685                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14686                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14687                   if(i == BOARD_RGHT) break;
14688                   board[CASTLING][5] = i;
14689                   c -= AAA;
14690                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14691                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14692                   if(c > i)
14693                       board[CASTLING][3] = c;
14694                   else
14695                       board[CASTLING][4] = c;
14696               } else { /* white rights */
14697                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14698                     if(board[0][i] == WhiteKing) break;
14699                   if(i == BOARD_RGHT) break;
14700                   board[CASTLING][2] = i;
14701                   c -= AAA - 'a' + 'A';
14702                   if(board[0][c] >= WhiteKing) break;
14703                   if(c > i)
14704                       board[CASTLING][0] = c;
14705                   else
14706                       board[CASTLING][1] = c;
14707               }
14708         }
14709       }
14710       for(i=0; i<nrCastlingRights; i++)
14711         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14712     if (appData.debugMode) {
14713         fprintf(debugFP, "FEN castling rights:");
14714         for(i=0; i<nrCastlingRights; i++)
14715         fprintf(debugFP, " %d", board[CASTLING][i]);
14716         fprintf(debugFP, "\n");
14717     }
14718
14719       while(*p==' ') p++;
14720     }
14721
14722     /* read e.p. field in games that know e.p. capture */
14723     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14724        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14725       if(*p=='-') {
14726         p++; board[EP_STATUS] = EP_NONE;
14727       } else {
14728          char c = *p++ - AAA;
14729
14730          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14731          if(*p >= '0' && *p <='9') *p++;
14732          board[EP_STATUS] = c;
14733       }
14734     }
14735
14736
14737     if(sscanf(p, "%d", &i) == 1) {
14738         FENrulePlies = i; /* 50-move ply counter */
14739         /* (The move number is still ignored)    */
14740     }
14741
14742     return TRUE;
14743 }
14744       
14745 void
14746 EditPositionPasteFEN(char *fen)
14747 {
14748   if (fen != NULL) {
14749     Board initial_position;
14750
14751     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14752       DisplayError(_("Bad FEN position in clipboard"), 0);
14753       return ;
14754     } else {
14755       int savedBlackPlaysFirst = blackPlaysFirst;
14756       EditPositionEvent();
14757       blackPlaysFirst = savedBlackPlaysFirst;
14758       CopyBoard(boards[0], initial_position);
14759       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14760       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14761       DisplayBothClocks();
14762       DrawPosition(FALSE, boards[currentMove]);
14763     }
14764   }
14765 }
14766
14767 static char cseq[12] = "\\   ";
14768
14769 Boolean set_cont_sequence(char *new_seq)
14770 {
14771     int len;
14772     Boolean ret;
14773
14774     // handle bad attempts to set the sequence
14775         if (!new_seq)
14776                 return 0; // acceptable error - no debug
14777
14778     len = strlen(new_seq);
14779     ret = (len > 0) && (len < sizeof(cseq));
14780     if (ret)
14781         strcpy(cseq, new_seq);
14782     else if (appData.debugMode)
14783         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14784     return ret;
14785 }
14786
14787 /*
14788     reformat a source message so words don't cross the width boundary.  internal
14789     newlines are not removed.  returns the wrapped size (no null character unless
14790     included in source message).  If dest is NULL, only calculate the size required
14791     for the dest buffer.  lp argument indicats line position upon entry, and it's
14792     passed back upon exit.
14793 */
14794 int wrap(char *dest, char *src, int count, int width, int *lp)
14795 {
14796     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14797
14798     cseq_len = strlen(cseq);
14799     old_line = line = *lp;
14800     ansi = len = clen = 0;
14801
14802     for (i=0; i < count; i++)
14803     {
14804         if (src[i] == '\033')
14805             ansi = 1;
14806
14807         // if we hit the width, back up
14808         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14809         {
14810             // store i & len in case the word is too long
14811             old_i = i, old_len = len;
14812
14813             // find the end of the last word
14814             while (i && src[i] != ' ' && src[i] != '\n')
14815             {
14816                 i--;
14817                 len--;
14818             }
14819
14820             // word too long?  restore i & len before splitting it
14821             if ((old_i-i+clen) >= width)
14822             {
14823                 i = old_i;
14824                 len = old_len;
14825             }
14826
14827             // extra space?
14828             if (i && src[i-1] == ' ')
14829                 len--;
14830
14831             if (src[i] != ' ' && src[i] != '\n')
14832             {
14833                 i--;
14834                 if (len)
14835                     len--;
14836             }
14837
14838             // now append the newline and continuation sequence
14839             if (dest)
14840                 dest[len] = '\n';
14841             len++;
14842             if (dest)
14843                 strncpy(dest+len, cseq, cseq_len);
14844             len += cseq_len;
14845             line = cseq_len;
14846             clen = cseq_len;
14847             continue;
14848         }
14849
14850         if (dest)
14851             dest[len] = src[i];
14852         len++;
14853         if (!ansi)
14854             line++;
14855         if (src[i] == '\n')
14856             line = 0;
14857         if (src[i] == 'm')
14858             ansi = 0;
14859     }
14860     if (dest && appData.debugMode)
14861     {
14862         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14863             count, width, line, len, *lp);
14864         show_bytes(debugFP, src, count);
14865         fprintf(debugFP, "\ndest: ");
14866         show_bytes(debugFP, dest, len);
14867         fprintf(debugFP, "\n");
14868     }
14869     *lp = dest ? line : old_line;
14870
14871     return len;
14872 }
14873
14874 // [HGM] vari: routines for shelving variations
14875
14876 void 
14877 PushTail(int firstMove, int lastMove)
14878 {
14879         int i, j, nrMoves = lastMove - firstMove;
14880
14881         if(appData.icsActive) { // only in local mode
14882                 forwardMostMove = currentMove; // mimic old ICS behavior
14883                 return;
14884         }
14885         if(storedGames >= MAX_VARIATIONS-1) return;
14886
14887         // push current tail of game on stack
14888         savedResult[storedGames] = gameInfo.result;
14889         savedDetails[storedGames] = gameInfo.resultDetails;
14890         gameInfo.resultDetails = NULL;
14891         savedFirst[storedGames] = firstMove;
14892         savedLast [storedGames] = lastMove;
14893         savedFramePtr[storedGames] = framePtr;
14894         framePtr -= nrMoves; // reserve space for the boards
14895         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14896             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14897             for(j=0; j<MOVE_LEN; j++)
14898                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14899             for(j=0; j<2*MOVE_LEN; j++)
14900                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14901             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14902             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14903             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14904             pvInfoList[firstMove+i-1].depth = 0;
14905             commentList[framePtr+i] = commentList[firstMove+i];
14906             commentList[firstMove+i] = NULL;
14907         }
14908
14909         storedGames++;
14910         forwardMostMove = currentMove; // truncte game so we can start variation
14911         if(storedGames == 1) GreyRevert(FALSE);
14912 }
14913
14914 Boolean
14915 PopTail(Boolean annotate)
14916 {
14917         int i, j, nrMoves;
14918         char buf[8000], moveBuf[20];
14919
14920         if(appData.icsActive) return FALSE; // only in local mode
14921         if(!storedGames) return FALSE; // sanity
14922
14923         storedGames--;
14924         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14925         nrMoves = savedLast[storedGames] - currentMove;
14926         if(annotate) {
14927                 int cnt = 10;
14928                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14929                 else strcpy(buf, "(");
14930                 for(i=currentMove; i<forwardMostMove; i++) {
14931                         if(WhiteOnMove(i))
14932                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14933                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14934                         strcat(buf, moveBuf);
14935                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14936                 }
14937                 strcat(buf, ")");
14938         }
14939         for(i=1; i<nrMoves; i++) { // copy last variation back
14940             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14941             for(j=0; j<MOVE_LEN; j++)
14942                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14943             for(j=0; j<2*MOVE_LEN; j++)
14944                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14945             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14946             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14947             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14948             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14949             commentList[currentMove+i] = commentList[framePtr+i];
14950             commentList[framePtr+i] = NULL;
14951         }
14952         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14953         framePtr = savedFramePtr[storedGames];
14954         gameInfo.result = savedResult[storedGames];
14955         if(gameInfo.resultDetails != NULL) {
14956             free(gameInfo.resultDetails);
14957       }
14958         gameInfo.resultDetails = savedDetails[storedGames];
14959         forwardMostMove = currentMove + nrMoves;
14960         if(storedGames == 0) GreyRevert(TRUE);
14961         return TRUE;
14962 }
14963
14964 void 
14965 CleanupTail()
14966 {       // remove all shelved variations
14967         int i;
14968         for(i=0; i<storedGames; i++) {
14969             if(savedDetails[i])
14970                 free(savedDetails[i]);
14971             savedDetails[i] = NULL;
14972         }
14973         for(i=framePtr; i<MAX_MOVES; i++) {
14974                 if(commentList[i]) free(commentList[i]);
14975                 commentList[i] = NULL;
14976         }
14977         framePtr = MAX_MOVES-1;
14978         storedGames = 0;
14979 }