Fix right-edge spillover of Seek-Graph dots in WinBoard
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                     } else
2546                     if(!suppressKibitz) // [HGM] kibitz
2547                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2548                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2549                         int nrDigit = 0, nrAlph = 0, j;
2550                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2551                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2552                         parse[parse_pos] = NULLCHAR;
2553                         // try to be smart: if it does not look like search info, it should go to
2554                         // ICS interaction window after all, not to engine-output window.
2555                         for(j=0; j<parse_pos; j++) { // count letters and digits
2556                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2557                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2558                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2559                         }
2560                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2561                             int depth=0; float score;
2562                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2563                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2564                                 pvInfoList[forwardMostMove-1].depth = depth;
2565                                 pvInfoList[forwardMostMove-1].score = 100*score;
2566                             }
2567                             OutputKibitz(suppressKibitz, parse);
2568                             next_out = i+1; // [HGM] suppress printing in ICS window
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                     }
2575                     started = STARTED_NONE;
2576                 } else {
2577                     /* Don't match patterns against characters in comment */
2578                     i++;
2579                     continue;
2580                 }
2581             }
2582             if (started == STARTED_CHATTER) {
2583                 if (buf[i] != '\n') {
2584                     /* Don't match patterns against characters in chatter */
2585                     i++;
2586                     continue;
2587                 }
2588                 started = STARTED_NONE;
2589             }
2590
2591             /* Kludge to deal with rcmd protocol */
2592             if (firstTime && looking_at(buf, &i, "\001*")) {
2593                 DisplayFatalError(&buf[1], 0, 1);
2594                 continue;
2595             } else {
2596                 firstTime = FALSE;
2597             }
2598
2599             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2600                 ics_type = ICS_ICC;
2601                 ics_prefix = "/";
2602                 if (appData.debugMode)
2603                   fprintf(debugFP, "ics_type %d\n", ics_type);
2604                 continue;
2605             }
2606             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2607                 ics_type = ICS_FICS;
2608                 ics_prefix = "$";
2609                 if (appData.debugMode)
2610                   fprintf(debugFP, "ics_type %d\n", ics_type);
2611                 continue;
2612             }
2613             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2614                 ics_type = ICS_CHESSNET;
2615                 ics_prefix = "/";
2616                 if (appData.debugMode)
2617                   fprintf(debugFP, "ics_type %d\n", ics_type);
2618                 continue;
2619             }
2620
2621             if (!loggedOn &&
2622                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2623                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2624                  looking_at(buf, &i, "will be \"*\""))) {
2625               strcpy(ics_handle, star_match[0]);
2626               continue;
2627             }
2628
2629             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2630               char buf[MSG_SIZ];
2631               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2632               DisplayIcsInteractionTitle(buf);
2633               have_set_title = TRUE;
2634             }
2635
2636             /* skip finger notes */
2637             if (started == STARTED_NONE &&
2638                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2639                  (buf[i] == '1' && buf[i+1] == '0')) &&
2640                 buf[i+2] == ':' && buf[i+3] == ' ') {
2641               started = STARTED_CHATTER;
2642               i += 3;
2643               continue;
2644             }
2645
2646             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2647             if(appData.seekGraph) {
2648                 if(soughtPending && MatchSoughtLine(buf+i)) {
2649                     i = strstr(buf+i, "rated") - buf;
2650                     next_out = leftover_start = i;
2651                     started = STARTED_CHATTER;
2652                     suppressKibitz = TRUE;
2653                     continue;
2654                 }
2655                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2656                         && looking_at(buf, &i, "* ads displayed")) {
2657                     soughtPending = FALSE;
2658                     seekGraphUp = TRUE;
2659                     DrawSeekGraph();
2660                     continue;
2661                 }
2662                 if(appData.autoRefresh) {
2663                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2664                         int s = (ics_type == ICS_ICC); // ICC format differs
2665                         if(seekGraphUp)
2666                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2667                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2668                         looking_at(buf, &i, "*% "); // eat prompt
2669                         next_out = i; // suppress
2670                         continue;
2671                     }
2672                     if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2673                         char *p = star_match[0];
2674                         while(*p) {
2675                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2676                             while(*p && *p++ != ' '); // next
2677                         }
2678                         looking_at(buf, &i, "*% "); // eat prompt
2679                         next_out = i;
2680                         continue;
2681                     }
2682                 }
2683             }
2684
2685             /* skip formula vars */
2686             if (started == STARTED_NONE &&
2687                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2688               started = STARTED_CHATTER;
2689               i += 3;
2690               continue;
2691             }
2692
2693             oldi = i;
2694             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2695             if (appData.autoKibitz && started == STARTED_NONE && 
2696                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2697                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2698                 if(looking_at(buf, &i, "* kibitzes: ") &&
2699                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2700                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2701                         suppressKibitz = TRUE;
2702                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2703                                 && (gameMode == IcsPlayingWhite)) ||
2704                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2705                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2706                             started = STARTED_CHATTER; // own kibitz we simply discard
2707                         else {
2708                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2709                             parse_pos = 0; parse[0] = NULLCHAR;
2710                             savingComment = TRUE;
2711                             suppressKibitz = gameMode != IcsObserving ? 2 :
2712                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2713                         } 
2714                         continue;
2715                 } else
2716                 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2717                     // suppress the acknowledgements of our own autoKibitz
2718                     char *p;
2719                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2720                     SendToPlayer(star_match[0], strlen(star_match[0]));
2721                     looking_at(buf, &i, "*% "); // eat prompt
2722                     next_out = i;
2723                 }
2724             } // [HGM] kibitz: end of patch
2725
2726 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2727
2728             // [HGM] chat: intercept tells by users for which we have an open chat window
2729             channel = -1;
2730             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2731                                            looking_at(buf, &i, "* whispers:") ||
2732                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2733                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2734                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2735                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2736                 int p;
2737                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2738                 chattingPartner = -1;
2739
2740                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2741                 for(p=0; p<MAX_CHAT; p++) {
2742                     if(channel == atoi(chatPartner[p])) {
2743                     talker[0] = '['; strcat(talker, "] ");
2744                     chattingPartner = p; break;
2745                     }
2746                 } else
2747                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2748                 for(p=0; p<MAX_CHAT; p++) {
2749                     if(!strcmp("WHISPER", chatPartner[p])) {
2750                         talker[0] = '['; strcat(talker, "] ");
2751                         chattingPartner = p; break;
2752                     }
2753                 }
2754                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2755                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2756                     talker[0] = 0;
2757                     chattingPartner = p; break;
2758                 }
2759                 if(chattingPartner<0) i = oldi; else {
2760                     started = STARTED_COMMENT;
2761                     parse_pos = 0; parse[0] = NULLCHAR;
2762                     savingComment = 3 + chattingPartner; // counts as TRUE
2763                     suppressKibitz = TRUE;
2764                 }
2765             } // [HGM] chat: end of patch
2766
2767             if (appData.zippyTalk || appData.zippyPlay) {
2768                 /* [DM] Backup address for color zippy lines */
2769                 backup = i;
2770 #if ZIPPY
2771        #ifdef WIN32
2772                if (loggedOn == TRUE)
2773                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2774                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2775        #else
2776                 if (ZippyControl(buf, &i) ||
2777                     ZippyConverse(buf, &i) ||
2778                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2779                       loggedOn = TRUE;
2780                       if (!appData.colorize) continue;
2781                 }
2782        #endif
2783 #endif
2784             } // [DM] 'else { ' deleted
2785                 if (
2786                     /* Regular tells and says */
2787                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2788                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2789                     looking_at(buf, &i, "* says: ") ||
2790                     /* Don't color "message" or "messages" output */
2791                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2792                     looking_at(buf, &i, "*. * at *:*: ") ||
2793                     looking_at(buf, &i, "--* (*:*): ") ||
2794                     /* Message notifications (same color as tells) */
2795                     looking_at(buf, &i, "* has left a message ") ||
2796                     looking_at(buf, &i, "* just sent you a message:\n") ||
2797                     /* Whispers and kibitzes */
2798                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2799                     looking_at(buf, &i, "* kibitzes: ") ||
2800                     /* Channel tells */
2801                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2802
2803                   if (tkind == 1 && strchr(star_match[0], ':')) {
2804                       /* Avoid "tells you:" spoofs in channels */
2805                      tkind = 3;
2806                   }
2807                   if (star_match[0][0] == NULLCHAR ||
2808                       strchr(star_match[0], ' ') ||
2809                       (tkind == 3 && strchr(star_match[1], ' '))) {
2810                     /* Reject bogus matches */
2811                     i = oldi;
2812                   } else {
2813                     if (appData.colorize) {
2814                       if (oldi > next_out) {
2815                         SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = oldi;
2817                       }
2818                       switch (tkind) {
2819                       case 1:
2820                         Colorize(ColorTell, FALSE);
2821                         curColor = ColorTell;
2822                         break;
2823                       case 2:
2824                         Colorize(ColorKibitz, FALSE);
2825                         curColor = ColorKibitz;
2826                         break;
2827                       case 3:
2828                         p = strrchr(star_match[1], '(');
2829                         if (p == NULL) {
2830                           p = star_match[1];
2831                         } else {
2832                           p++;
2833                         }
2834                         if (atoi(p) == 1) {
2835                           Colorize(ColorChannel1, FALSE);
2836                           curColor = ColorChannel1;
2837                         } else {
2838                           Colorize(ColorChannel, FALSE);
2839                           curColor = ColorChannel;
2840                         }
2841                         break;
2842                       case 5:
2843                         curColor = ColorNormal;
2844                         break;
2845                       }
2846                     }
2847                     if (started == STARTED_NONE && appData.autoComment &&
2848                         (gameMode == IcsObserving ||
2849                          gameMode == IcsPlayingWhite ||
2850                          gameMode == IcsPlayingBlack)) {
2851                       parse_pos = i - oldi;
2852                       memcpy(parse, &buf[oldi], parse_pos);
2853                       parse[parse_pos] = NULLCHAR;
2854                       started = STARTED_COMMENT;
2855                       savingComment = TRUE;
2856                     } else {
2857                       started = STARTED_CHATTER;
2858                       savingComment = FALSE;
2859                     }
2860                     loggedOn = TRUE;
2861                     continue;
2862                   }
2863                 }
2864
2865                 if (looking_at(buf, &i, "* s-shouts: ") ||
2866                     looking_at(buf, &i, "* c-shouts: ")) {
2867                     if (appData.colorize) {
2868                         if (oldi > next_out) {
2869                             SendToPlayer(&buf[next_out], oldi - next_out);
2870                             next_out = oldi;
2871                         }
2872                         Colorize(ColorSShout, FALSE);
2873                         curColor = ColorSShout;
2874                     }
2875                     loggedOn = TRUE;
2876                     started = STARTED_CHATTER;
2877                     continue;
2878                 }
2879
2880                 if (looking_at(buf, &i, "--->")) {
2881                     loggedOn = TRUE;
2882                     continue;
2883                 }
2884
2885                 if (looking_at(buf, &i, "* shouts: ") ||
2886                     looking_at(buf, &i, "--> ")) {
2887                     if (appData.colorize) {
2888                         if (oldi > next_out) {
2889                             SendToPlayer(&buf[next_out], oldi - next_out);
2890                             next_out = oldi;
2891                         }
2892                         Colorize(ColorShout, FALSE);
2893                         curColor = ColorShout;
2894                     }
2895                     loggedOn = TRUE;
2896                     started = STARTED_CHATTER;
2897                     continue;
2898                 }
2899
2900                 if (looking_at( buf, &i, "Challenge:")) {
2901                     if (appData.colorize) {
2902                         if (oldi > next_out) {
2903                             SendToPlayer(&buf[next_out], oldi - next_out);
2904                             next_out = oldi;
2905                         }
2906                         Colorize(ColorChallenge, FALSE);
2907                         curColor = ColorChallenge;
2908                     }
2909                     loggedOn = TRUE;
2910                     continue;
2911                 }
2912
2913                 if (looking_at(buf, &i, "* offers you") ||
2914                     looking_at(buf, &i, "* offers to be") ||
2915                     looking_at(buf, &i, "* would like to") ||
2916                     looking_at(buf, &i, "* requests to") ||
2917                     looking_at(buf, &i, "Your opponent offers") ||
2918                     looking_at(buf, &i, "Your opponent requests")) {
2919
2920                     if (appData.colorize) {
2921                         if (oldi > next_out) {
2922                             SendToPlayer(&buf[next_out], oldi - next_out);
2923                             next_out = oldi;
2924                         }
2925                         Colorize(ColorRequest, FALSE);
2926                         curColor = ColorRequest;
2927                     }
2928                     continue;
2929                 }
2930
2931                 if (looking_at(buf, &i, "* (*) seeking")) {
2932                     if (appData.colorize) {
2933                         if (oldi > next_out) {
2934                             SendToPlayer(&buf[next_out], oldi - next_out);
2935                             next_out = oldi;
2936                         }
2937                         Colorize(ColorSeek, FALSE);
2938                         curColor = ColorSeek;
2939                     }
2940                     continue;
2941             }
2942
2943             if (looking_at(buf, &i, "\\   ")) {
2944                 if (prevColor != ColorNormal) {
2945                     if (oldi > next_out) {
2946                         SendToPlayer(&buf[next_out], oldi - next_out);
2947                         next_out = oldi;
2948                     }
2949                     Colorize(prevColor, TRUE);
2950                     curColor = prevColor;
2951                 }
2952                 if (savingComment) {
2953                     parse_pos = i - oldi;
2954                     memcpy(parse, &buf[oldi], parse_pos);
2955                     parse[parse_pos] = NULLCHAR;
2956                     started = STARTED_COMMENT;
2957                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2958                         chattingPartner = savingComment - 3; // kludge to remember the box
2959                 } else {
2960                     started = STARTED_CHATTER;
2961                 }
2962                 continue;
2963             }
2964
2965             if (looking_at(buf, &i, "Black Strength :") ||
2966                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2967                 looking_at(buf, &i, "<10>") ||
2968                 looking_at(buf, &i, "#@#")) {
2969                 /* Wrong board style */
2970                 loggedOn = TRUE;
2971                 SendToICS(ics_prefix);
2972                 SendToICS("set style 12\n");
2973                 SendToICS(ics_prefix);
2974                 SendToICS("refresh\n");
2975                 continue;
2976             }
2977             
2978             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2979                 ICSInitScript();
2980                 have_sent_ICS_logon = 1;
2981                 continue;
2982             }
2983               
2984             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
2985                 (looking_at(buf, &i, "\n<12> ") ||
2986                  looking_at(buf, &i, "<12> "))) {
2987                 loggedOn = TRUE;
2988                 if (oldi > next_out) {
2989                     SendToPlayer(&buf[next_out], oldi - next_out);
2990                 }
2991                 next_out = i;
2992                 started = STARTED_BOARD;
2993                 parse_pos = 0;
2994                 continue;
2995             }
2996
2997             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2998                 looking_at(buf, &i, "<b1> ")) {
2999                 if (oldi > next_out) {
3000                     SendToPlayer(&buf[next_out], oldi - next_out);
3001                 }
3002                 next_out = i;
3003                 started = STARTED_HOLDINGS;
3004                 parse_pos = 0;
3005                 continue;
3006             }
3007
3008             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3009                 loggedOn = TRUE;
3010                 /* Header for a move list -- first line */
3011
3012                 switch (ics_getting_history) {
3013                   case H_FALSE:
3014                     switch (gameMode) {
3015                       case IcsIdle:
3016                       case BeginningOfGame:
3017                         /* User typed "moves" or "oldmoves" while we
3018                            were idle.  Pretend we asked for these
3019                            moves and soak them up so user can step
3020                            through them and/or save them.
3021                            */
3022                         Reset(FALSE, TRUE);
3023                         gameMode = IcsObserving;
3024                         ModeHighlight();
3025                         ics_gamenum = -1;
3026                         ics_getting_history = H_GOT_UNREQ_HEADER;
3027                         break;
3028                       case EditGame: /*?*/
3029                       case EditPosition: /*?*/
3030                         /* Should above feature work in these modes too? */
3031                         /* For now it doesn't */
3032                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3033                         break;
3034                       default:
3035                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3036                         break;
3037                     }
3038                     break;
3039                   case H_REQUESTED:
3040                     /* Is this the right one? */
3041                     if (gameInfo.white && gameInfo.black &&
3042                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3043                         strcmp(gameInfo.black, star_match[2]) == 0) {
3044                         /* All is well */
3045                         ics_getting_history = H_GOT_REQ_HEADER;
3046                     }
3047                     break;
3048                   case H_GOT_REQ_HEADER:
3049                   case H_GOT_UNREQ_HEADER:
3050                   case H_GOT_UNWANTED_HEADER:
3051                   case H_GETTING_MOVES:
3052                     /* Should not happen */
3053                     DisplayError(_("Error gathering move list: two headers"), 0);
3054                     ics_getting_history = H_FALSE;
3055                     break;
3056                 }
3057
3058                 /* Save player ratings into gameInfo if needed */
3059                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3060                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3061                     (gameInfo.whiteRating == -1 ||
3062                      gameInfo.blackRating == -1)) {
3063
3064                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3065                     gameInfo.blackRating = string_to_rating(star_match[3]);
3066                     if (appData.debugMode)
3067                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3068                               gameInfo.whiteRating, gameInfo.blackRating);
3069                 }
3070                 continue;
3071             }
3072
3073             if (looking_at(buf, &i,
3074               "* * match, initial time: * minute*, increment: * second")) {
3075                 /* Header for a move list -- second line */
3076                 /* Initial board will follow if this is a wild game */
3077                 if (gameInfo.event != NULL) free(gameInfo.event);
3078                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3079                 gameInfo.event = StrSave(str);
3080                 /* [HGM] we switched variant. Translate boards if needed. */
3081                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3082                 continue;
3083             }
3084
3085             if (looking_at(buf, &i, "Move  ")) {
3086                 /* Beginning of a move list */
3087                 switch (ics_getting_history) {
3088                   case H_FALSE:
3089                     /* Normally should not happen */
3090                     /* Maybe user hit reset while we were parsing */
3091                     break;
3092                   case H_REQUESTED:
3093                     /* Happens if we are ignoring a move list that is not
3094                      * the one we just requested.  Common if the user
3095                      * tries to observe two games without turning off
3096                      * getMoveList */
3097                     break;
3098                   case H_GETTING_MOVES:
3099                     /* Should not happen */
3100                     DisplayError(_("Error gathering move list: nested"), 0);
3101                     ics_getting_history = H_FALSE;
3102                     break;
3103                   case H_GOT_REQ_HEADER:
3104                     ics_getting_history = H_GETTING_MOVES;
3105                     started = STARTED_MOVES;
3106                     parse_pos = 0;
3107                     if (oldi > next_out) {
3108                         SendToPlayer(&buf[next_out], oldi - next_out);
3109                     }
3110                     break;
3111                   case H_GOT_UNREQ_HEADER:
3112                     ics_getting_history = H_GETTING_MOVES;
3113                     started = STARTED_MOVES_NOHIDE;
3114                     parse_pos = 0;
3115                     break;
3116                   case H_GOT_UNWANTED_HEADER:
3117                     ics_getting_history = H_FALSE;
3118                     break;
3119                 }
3120                 continue;
3121             }                           
3122             
3123             if (looking_at(buf, &i, "% ") ||
3124                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3125                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3126                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3127                     soughtPending = FALSE;
3128                     seekGraphUp = TRUE;
3129                     DrawSeekGraph();
3130                 }
3131                 if(suppressKibitz) next_out = i;
3132                 savingComment = FALSE;
3133                 suppressKibitz = 0;
3134                 switch (started) {
3135                   case STARTED_MOVES:
3136                   case STARTED_MOVES_NOHIDE:
3137                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3138                     parse[parse_pos + i - oldi] = NULLCHAR;
3139                     ParseGameHistory(parse);
3140 #if ZIPPY
3141                     if (appData.zippyPlay && first.initDone) {
3142                         FeedMovesToProgram(&first, forwardMostMove);
3143                         if (gameMode == IcsPlayingWhite) {
3144                             if (WhiteOnMove(forwardMostMove)) {
3145                                 if (first.sendTime) {
3146                                   if (first.useColors) {
3147                                     SendToProgram("black\n", &first); 
3148                                   }
3149                                   SendTimeRemaining(&first, TRUE);
3150                                 }
3151                                 if (first.useColors) {
3152                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3153                                 }
3154                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3155                                 first.maybeThinking = TRUE;
3156                             } else {
3157                                 if (first.usePlayother) {
3158                                   if (first.sendTime) {
3159                                     SendTimeRemaining(&first, TRUE);
3160                                   }
3161                                   SendToProgram("playother\n", &first);
3162                                   firstMove = FALSE;
3163                                 } else {
3164                                   firstMove = TRUE;
3165                                 }
3166                             }
3167                         } else if (gameMode == IcsPlayingBlack) {
3168                             if (!WhiteOnMove(forwardMostMove)) {
3169                                 if (first.sendTime) {
3170                                   if (first.useColors) {
3171                                     SendToProgram("white\n", &first);
3172                                   }
3173                                   SendTimeRemaining(&first, FALSE);
3174                                 }
3175                                 if (first.useColors) {
3176                                   SendToProgram("black\n", &first);
3177                                 }
3178                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3179                                 first.maybeThinking = TRUE;
3180                             } else {
3181                                 if (first.usePlayother) {
3182                                   if (first.sendTime) {
3183                                     SendTimeRemaining(&first, FALSE);
3184                                   }
3185                                   SendToProgram("playother\n", &first);
3186                                   firstMove = FALSE;
3187                                 } else {
3188                                   firstMove = TRUE;
3189                                 }
3190                             }
3191                         }                       
3192                     }
3193 #endif
3194                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3195                         /* Moves came from oldmoves or moves command
3196                            while we weren't doing anything else.
3197                            */
3198                         currentMove = forwardMostMove;
3199                         ClearHighlights();/*!!could figure this out*/
3200                         flipView = appData.flipView;
3201                         DrawPosition(TRUE, boards[currentMove]);
3202                         DisplayBothClocks();
3203                         sprintf(str, "%s vs. %s",
3204                                 gameInfo.white, gameInfo.black);
3205                         DisplayTitle(str);
3206                         gameMode = IcsIdle;
3207                     } else {
3208                         /* Moves were history of an active game */
3209                         if (gameInfo.resultDetails != NULL) {
3210                             free(gameInfo.resultDetails);
3211                             gameInfo.resultDetails = NULL;
3212                         }
3213                     }
3214                     HistorySet(parseList, backwardMostMove,
3215                                forwardMostMove, currentMove-1);
3216                     DisplayMove(currentMove - 1);
3217                     if (started == STARTED_MOVES) next_out = i;
3218                     started = STARTED_NONE;
3219                     ics_getting_history = H_FALSE;
3220                     break;
3221
3222                   case STARTED_OBSERVE:
3223                     started = STARTED_NONE;
3224                     SendToICS(ics_prefix);
3225                     SendToICS("refresh\n");
3226                     break;
3227
3228                   default:
3229                     break;
3230                 }
3231                 if(bookHit) { // [HGM] book: simulate book reply
3232                     static char bookMove[MSG_SIZ]; // a bit generous?
3233
3234                     programStats.nodes = programStats.depth = programStats.time = 
3235                     programStats.score = programStats.got_only_move = 0;
3236                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3237
3238                     strcpy(bookMove, "move ");
3239                     strcat(bookMove, bookHit);
3240                     HandleMachineMove(bookMove, &first);
3241                 }
3242                 continue;
3243             }
3244             
3245             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3246                  started == STARTED_HOLDINGS ||
3247                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3248                 /* Accumulate characters in move list or board */
3249                 parse[parse_pos++] = buf[i];
3250             }
3251             
3252             /* Start of game messages.  Mostly we detect start of game
3253                when the first board image arrives.  On some versions
3254                of the ICS, though, we need to do a "refresh" after starting
3255                to observe in order to get the current board right away. */
3256             if (looking_at(buf, &i, "Adding game * to observation list")) {
3257                 started = STARTED_OBSERVE;
3258                 continue;
3259             }
3260
3261             /* Handle auto-observe */
3262             if (appData.autoObserve &&
3263                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3264                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3265                 char *player;
3266                 /* Choose the player that was highlighted, if any. */
3267                 if (star_match[0][0] == '\033' ||
3268                     star_match[1][0] != '\033') {
3269                     player = star_match[0];
3270                 } else {
3271                     player = star_match[2];
3272                 }
3273                 sprintf(str, "%sobserve %s\n",
3274                         ics_prefix, StripHighlightAndTitle(player));
3275                 SendToICS(str);
3276
3277                 /* Save ratings from notify string */
3278                 strcpy(player1Name, star_match[0]);
3279                 player1Rating = string_to_rating(star_match[1]);
3280                 strcpy(player2Name, star_match[2]);
3281                 player2Rating = string_to_rating(star_match[3]);
3282
3283                 if (appData.debugMode)
3284                   fprintf(debugFP, 
3285                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3286                           player1Name, player1Rating,
3287                           player2Name, player2Rating);
3288
3289                 continue;
3290             }
3291
3292             /* Deal with automatic examine mode after a game,
3293                and with IcsObserving -> IcsExamining transition */
3294             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3295                 looking_at(buf, &i, "has made you an examiner of game *")) {
3296
3297                 int gamenum = atoi(star_match[0]);
3298                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3299                     gamenum == ics_gamenum) {
3300                     /* We were already playing or observing this game;
3301                        no need to refetch history */
3302                     gameMode = IcsExamining;
3303                     if (pausing) {
3304                         pauseExamForwardMostMove = forwardMostMove;
3305                     } else if (currentMove < forwardMostMove) {
3306                         ForwardInner(forwardMostMove);
3307                     }
3308                 } else {
3309                     /* I don't think this case really can happen */
3310                     SendToICS(ics_prefix);
3311                     SendToICS("refresh\n");
3312                 }
3313                 continue;
3314             }    
3315             
3316             /* Error messages */
3317 //          if (ics_user_moved) {
3318             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3319                 if (looking_at(buf, &i, "Illegal move") ||
3320                     looking_at(buf, &i, "Not a legal move") ||
3321                     looking_at(buf, &i, "Your king is in check") ||
3322                     looking_at(buf, &i, "It isn't your turn") ||
3323                     looking_at(buf, &i, "It is not your move")) {
3324                     /* Illegal move */
3325                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3326                         currentMove = forwardMostMove-1;
3327                         DisplayMove(currentMove - 1); /* before DMError */
3328                         DrawPosition(FALSE, boards[currentMove]);
3329                         SwitchClocks(forwardMostMove-1); // [HGM] race
3330                         DisplayBothClocks();
3331                     }
3332                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3333                     ics_user_moved = 0;
3334                     continue;
3335                 }
3336             }
3337
3338             if (looking_at(buf, &i, "still have time") ||
3339                 looking_at(buf, &i, "not out of time") ||
3340                 looking_at(buf, &i, "either player is out of time") ||
3341                 looking_at(buf, &i, "has timeseal; checking")) {
3342                 /* We must have called his flag a little too soon */
3343                 whiteFlag = blackFlag = FALSE;
3344                 continue;
3345             }
3346
3347             if (looking_at(buf, &i, "added * seconds to") ||
3348                 looking_at(buf, &i, "seconds were added to")) {
3349                 /* Update the clocks */
3350                 SendToICS(ics_prefix);
3351                 SendToICS("refresh\n");
3352                 continue;
3353             }
3354
3355             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3356                 ics_clock_paused = TRUE;
3357                 StopClocks();
3358                 continue;
3359             }
3360
3361             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3362                 ics_clock_paused = FALSE;
3363                 StartClocks();
3364                 continue;
3365             }
3366
3367             /* Grab player ratings from the Creating: message.
3368                Note we have to check for the special case when
3369                the ICS inserts things like [white] or [black]. */
3370             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3371                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3372                 /* star_matches:
3373                    0    player 1 name (not necessarily white)
3374                    1    player 1 rating
3375                    2    empty, white, or black (IGNORED)
3376                    3    player 2 name (not necessarily black)
3377                    4    player 2 rating
3378                    
3379                    The names/ratings are sorted out when the game
3380                    actually starts (below).
3381                 */
3382                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3383                 player1Rating = string_to_rating(star_match[1]);
3384                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3385                 player2Rating = string_to_rating(star_match[4]);
3386
3387                 if (appData.debugMode)
3388                   fprintf(debugFP, 
3389                           "Ratings from 'Creating:' %s %d, %s %d\n",
3390                           player1Name, player1Rating,
3391                           player2Name, player2Rating);
3392
3393                 continue;
3394             }
3395             
3396             /* Improved generic start/end-of-game messages */
3397             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3398                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3399                 /* If tkind == 0: */
3400                 /* star_match[0] is the game number */
3401                 /*           [1] is the white player's name */
3402                 /*           [2] is the black player's name */
3403                 /* For end-of-game: */
3404                 /*           [3] is the reason for the game end */
3405                 /*           [4] is a PGN end game-token, preceded by " " */
3406                 /* For start-of-game: */
3407                 /*           [3] begins with "Creating" or "Continuing" */
3408                 /*           [4] is " *" or empty (don't care). */
3409                 int gamenum = atoi(star_match[0]);
3410                 char *whitename, *blackname, *why, *endtoken;
3411                 ChessMove endtype = (ChessMove) 0;
3412
3413                 if (tkind == 0) {
3414                   whitename = star_match[1];
3415                   blackname = star_match[2];
3416                   why = star_match[3];
3417                   endtoken = star_match[4];
3418                 } else {
3419                   whitename = star_match[1];
3420                   blackname = star_match[3];
3421                   why = star_match[5];
3422                   endtoken = star_match[6];
3423                 }
3424
3425                 /* Game start messages */
3426                 if (strncmp(why, "Creating ", 9) == 0 ||
3427                     strncmp(why, "Continuing ", 11) == 0) {
3428                     gs_gamenum = gamenum;
3429                     strcpy(gs_kind, strchr(why, ' ') + 1);
3430                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3431 #if ZIPPY
3432                     if (appData.zippyPlay) {
3433                         ZippyGameStart(whitename, blackname);
3434                     }
3435 #endif /*ZIPPY*/
3436                     continue;
3437                 }
3438
3439                 /* Game end messages */
3440                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3441                     ics_gamenum != gamenum) {
3442                     continue;
3443                 }
3444                 while (endtoken[0] == ' ') endtoken++;
3445                 switch (endtoken[0]) {
3446                   case '*':
3447                   default:
3448                     endtype = GameUnfinished;
3449                     break;
3450                   case '0':
3451                     endtype = BlackWins;
3452                     break;
3453                   case '1':
3454                     if (endtoken[1] == '/')
3455                       endtype = GameIsDrawn;
3456                     else
3457                       endtype = WhiteWins;
3458                     break;
3459                 }
3460                 GameEnds(endtype, why, GE_ICS);
3461 #if ZIPPY
3462                 if (appData.zippyPlay && first.initDone) {
3463                     ZippyGameEnd(endtype, why);
3464                     if (first.pr == NULL) {
3465                       /* Start the next process early so that we'll
3466                          be ready for the next challenge */
3467                       StartChessProgram(&first);
3468                     }
3469                     /* Send "new" early, in case this command takes
3470                        a long time to finish, so that we'll be ready
3471                        for the next challenge. */
3472                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3473                     Reset(TRUE, TRUE);
3474                 }
3475 #endif /*ZIPPY*/
3476                 continue;
3477             }
3478
3479             if (looking_at(buf, &i, "Removing game * from observation") ||
3480                 looking_at(buf, &i, "no longer observing game *") ||
3481                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3482                 if (gameMode == IcsObserving &&
3483                     atoi(star_match[0]) == ics_gamenum)
3484                   {
3485                       /* icsEngineAnalyze */
3486                       if (appData.icsEngineAnalyze) {
3487                             ExitAnalyzeMode();
3488                             ModeHighlight();
3489                       }
3490                       StopClocks();
3491                       gameMode = IcsIdle;
3492                       ics_gamenum = -1;
3493                       ics_user_moved = FALSE;
3494                   }
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i, "no longer examining game *")) {
3499                 if (gameMode == IcsExamining &&
3500                     atoi(star_match[0]) == ics_gamenum)
3501                   {
3502                       gameMode = IcsIdle;
3503                       ics_gamenum = -1;
3504                       ics_user_moved = FALSE;
3505                   }
3506                 continue;
3507             }
3508
3509             /* Advance leftover_start past any newlines we find,
3510                so only partial lines can get reparsed */
3511             if (looking_at(buf, &i, "\n")) {
3512                 prevColor = curColor;
3513                 if (curColor != ColorNormal) {
3514                     if (oldi > next_out) {
3515                         SendToPlayer(&buf[next_out], oldi - next_out);
3516                         next_out = oldi;
3517                     }
3518                     Colorize(ColorNormal, FALSE);
3519                     curColor = ColorNormal;
3520                 }
3521                 if (started == STARTED_BOARD) {
3522                     started = STARTED_NONE;
3523                     parse[parse_pos] = NULLCHAR;
3524                     ParseBoard12(parse);
3525                     ics_user_moved = 0;
3526
3527                     /* Send premove here */
3528                     if (appData.premove) {
3529                       char str[MSG_SIZ];
3530                       if (currentMove == 0 &&
3531                           gameMode == IcsPlayingWhite &&
3532                           appData.premoveWhite) {
3533                         sprintf(str, "%s\n", appData.premoveWhiteText);
3534                         if (appData.debugMode)
3535                           fprintf(debugFP, "Sending premove:\n");
3536                         SendToICS(str);
3537                       } else if (currentMove == 1 &&
3538                                  gameMode == IcsPlayingBlack &&
3539                                  appData.premoveBlack) {
3540                         sprintf(str, "%s\n", appData.premoveBlackText);
3541                         if (appData.debugMode)
3542                           fprintf(debugFP, "Sending premove:\n");
3543                         SendToICS(str);
3544                       } else if (gotPremove) {
3545                         gotPremove = 0;
3546                         ClearPremoveHighlights();
3547                         if (appData.debugMode)
3548                           fprintf(debugFP, "Sending premove:\n");
3549                           UserMoveEvent(premoveFromX, premoveFromY, 
3550                                         premoveToX, premoveToY, 
3551                                         premovePromoChar);
3552                       }
3553                     }
3554
3555                     /* Usually suppress following prompt */
3556                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3557                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3558                         if (looking_at(buf, &i, "*% ")) {
3559                             savingComment = FALSE;
3560                             suppressKibitz = 0;
3561                         }
3562                     }
3563                     next_out = i;
3564                 } else if (started == STARTED_HOLDINGS) {
3565                     int gamenum;
3566                     char new_piece[MSG_SIZ];
3567                     started = STARTED_NONE;
3568                     parse[parse_pos] = NULLCHAR;
3569                     if (appData.debugMode)
3570                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3571                                                         parse, currentMove);
3572                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3573                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3574                         if (gameInfo.variant == VariantNormal) {
3575                           /* [HGM] We seem to switch variant during a game!
3576                            * Presumably no holdings were displayed, so we have
3577                            * to move the position two files to the right to
3578                            * create room for them!
3579                            */
3580                           VariantClass newVariant;
3581                           switch(gameInfo.boardWidth) { // base guess on board width
3582                                 case 9:  newVariant = VariantShogi; break;
3583                                 case 10: newVariant = VariantGreat; break;
3584                                 default: newVariant = VariantCrazyhouse; break;
3585                           }
3586                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3587                           /* Get a move list just to see the header, which
3588                              will tell us whether this is really bug or zh */
3589                           if (ics_getting_history == H_FALSE) {
3590                             ics_getting_history = H_REQUESTED;
3591                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3592                             SendToICS(str);
3593                           }
3594                         }
3595                         new_piece[0] = NULLCHAR;
3596                         sscanf(parse, "game %d white [%s black [%s <- %s",
3597                                &gamenum, white_holding, black_holding,
3598                                new_piece);
3599                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3600                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3601                         /* [HGM] copy holdings to board holdings area */
3602                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3603                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3604                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3605 #if ZIPPY
3606                         if (appData.zippyPlay && first.initDone) {
3607                             ZippyHoldings(white_holding, black_holding,
3608                                           new_piece);
3609                         }
3610 #endif /*ZIPPY*/
3611                         if (tinyLayout || smallLayout) {
3612                             char wh[16], bh[16];
3613                             PackHolding(wh, white_holding);
3614                             PackHolding(bh, black_holding);
3615                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3616                                     gameInfo.white, gameInfo.black);
3617                         } else {
3618                             sprintf(str, "%s [%s] vs. %s [%s]",
3619                                     gameInfo.white, white_holding,
3620                                     gameInfo.black, black_holding);
3621                         }
3622
3623                         DrawPosition(FALSE, boards[currentMove]);
3624                         DisplayTitle(str);
3625                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3626                         sscanf(parse, "game %d white [%s black [%s <- %s",
3627                                &gamenum, white_holding, black_holding,
3628                                new_piece);
3629                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3630                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3631                         /* [HGM] copy holdings to partner-board holdings area */
3632                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3633                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3634                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3635                       }
3636                     }
3637                     /* Suppress following prompt */
3638                     if (looking_at(buf, &i, "*% ")) {
3639                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3640                         savingComment = FALSE;
3641                         suppressKibitz = 0;
3642                     }
3643                     next_out = i;
3644                 }
3645                 continue;
3646             }
3647
3648             i++;                /* skip unparsed character and loop back */
3649         }
3650         
3651         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3652 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3653 //          SendToPlayer(&buf[next_out], i - next_out);
3654             started != STARTED_HOLDINGS && leftover_start > next_out) {
3655             SendToPlayer(&buf[next_out], leftover_start - next_out);
3656             next_out = i;
3657         }
3658         
3659         leftover_len = buf_len - leftover_start;
3660         /* if buffer ends with something we couldn't parse,
3661            reparse it after appending the next read */
3662         
3663     } else if (count == 0) {
3664         RemoveInputSource(isr);
3665         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3666     } else {
3667         DisplayFatalError(_("Error reading from ICS"), error, 1);
3668     }
3669 }
3670
3671
3672 /* Board style 12 looks like this:
3673    
3674    <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
3675    
3676  * The "<12> " is stripped before it gets to this routine.  The two
3677  * trailing 0's (flip state and clock ticking) are later addition, and
3678  * some chess servers may not have them, or may have only the first.
3679  * Additional trailing fields may be added in the future.  
3680  */
3681
3682 #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"
3683
3684 #define RELATION_OBSERVING_PLAYED    0
3685 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3686 #define RELATION_PLAYING_MYMOVE      1
3687 #define RELATION_PLAYING_NOTMYMOVE  -1
3688 #define RELATION_EXAMINING           2
3689 #define RELATION_ISOLATED_BOARD     -3
3690 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3691
3692 void
3693 ParseBoard12(string)
3694      char *string;
3695
3696     GameMode newGameMode;
3697     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3698     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3699     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3700     char to_play, board_chars[200];
3701     char move_str[500], str[500], elapsed_time[500];
3702     char black[32], white[32];
3703     Board board;
3704     int prevMove = currentMove;
3705     int ticking = 2;
3706     ChessMove moveType;
3707     int fromX, fromY, toX, toY;
3708     char promoChar;
3709     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3710     char *bookHit = NULL; // [HGM] book
3711     Boolean weird = FALSE, reqFlag = FALSE;
3712
3713     fromX = fromY = toX = toY = -1;
3714     
3715     newGame = FALSE;
3716
3717     if (appData.debugMode)
3718       fprintf(debugFP, _("Parsing board: %s\n"), string);
3719
3720     move_str[0] = NULLCHAR;
3721     elapsed_time[0] = NULLCHAR;
3722     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3723         int  i = 0, j;
3724         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3725             if(string[i] == ' ') { ranks++; files = 0; }
3726             else files++;
3727             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3728             i++;
3729         }
3730         for(j = 0; j <i; j++) board_chars[j] = string[j];
3731         board_chars[i] = '\0';
3732         string += i + 1;
3733     }
3734     n = sscanf(string, PATTERN, &to_play, &double_push,
3735                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3736                &gamenum, white, black, &relation, &basetime, &increment,
3737                &white_stren, &black_stren, &white_time, &black_time,
3738                &moveNum, str, elapsed_time, move_str, &ics_flip,
3739                &ticking);
3740
3741     if (n < 21) {
3742         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3743         DisplayError(str, 0);
3744         return;
3745     }
3746
3747     /* Convert the move number to internal form */
3748     moveNum = (moveNum - 1) * 2;
3749     if (to_play == 'B') moveNum++;
3750     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3751       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3752                         0, 1);
3753       return;
3754     }
3755     
3756     switch (relation) {
3757       case RELATION_OBSERVING_PLAYED:
3758       case RELATION_OBSERVING_STATIC:
3759         if (gamenum == -1) {
3760             /* Old ICC buglet */
3761             relation = RELATION_OBSERVING_STATIC;
3762         }
3763         newGameMode = IcsObserving;
3764         break;
3765       case RELATION_PLAYING_MYMOVE:
3766       case RELATION_PLAYING_NOTMYMOVE:
3767         newGameMode =
3768           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3769             IcsPlayingWhite : IcsPlayingBlack;
3770         break;
3771       case RELATION_EXAMINING:
3772         newGameMode = IcsExamining;
3773         break;
3774       case RELATION_ISOLATED_BOARD:
3775       default:
3776         /* Just display this board.  If user was doing something else,
3777            we will forget about it until the next board comes. */ 
3778         newGameMode = IcsIdle;
3779         break;
3780       case RELATION_STARTING_POSITION:
3781         newGameMode = gameMode;
3782         break;
3783     }
3784     
3785     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3786          && newGameMode == IcsObserving && appData.bgObserve) {
3787       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3788       char buf[MSG_SIZ];
3789       for (k = 0; k < ranks; k++) {
3790         for (j = 0; j < files; j++)
3791           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3792         if(gameInfo.holdingsWidth > 1) {
3793              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3794              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3795         }
3796       }
3797       CopyBoard(partnerBoard, board);
3798       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3799       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3800                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3801       DisplayMessage(buf, "");
3802       return;
3803     }
3804
3805     /* Modify behavior for initial board display on move listing
3806        of wild games.
3807        */
3808     switch (ics_getting_history) {
3809       case H_FALSE:
3810       case H_REQUESTED:
3811         break;
3812       case H_GOT_REQ_HEADER:
3813       case H_GOT_UNREQ_HEADER:
3814         /* This is the initial position of the current game */
3815         gamenum = ics_gamenum;
3816         moveNum = 0;            /* old ICS bug workaround */
3817         if (to_play == 'B') {
3818           startedFromSetupPosition = TRUE;
3819           blackPlaysFirst = TRUE;
3820           moveNum = 1;
3821           if (forwardMostMove == 0) forwardMostMove = 1;
3822           if (backwardMostMove == 0) backwardMostMove = 1;
3823           if (currentMove == 0) currentMove = 1;
3824         }
3825         newGameMode = gameMode;
3826         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3827         break;
3828       case H_GOT_UNWANTED_HEADER:
3829         /* This is an initial board that we don't want */
3830         return;
3831       case H_GETTING_MOVES:
3832         /* Should not happen */
3833         DisplayError(_("Error gathering move list: extra board"), 0);
3834         ics_getting_history = H_FALSE;
3835         return;
3836     }
3837
3838    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3839                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3840      /* [HGM] We seem to have switched variant unexpectedly
3841       * Try to guess new variant from board size
3842       */
3843           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3844           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3845           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3846           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3847           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3848           if(!weird) newVariant = VariantNormal;
3849           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3850           /* Get a move list just to see the header, which
3851              will tell us whether this is really bug or zh */
3852           if (ics_getting_history == H_FALSE) {
3853             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3854             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3855             SendToICS(str);
3856           }
3857     }
3858     
3859     /* Take action if this is the first board of a new game, or of a
3860        different game than is currently being displayed.  */
3861     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3862         relation == RELATION_ISOLATED_BOARD) {
3863         
3864         /* Forget the old game and get the history (if any) of the new one */
3865         if (gameMode != BeginningOfGame) {
3866           Reset(TRUE, TRUE);
3867         }
3868         newGame = TRUE;
3869         if (appData.autoRaiseBoard) BoardToTop();
3870         prevMove = -3;
3871         if (gamenum == -1) {
3872             newGameMode = IcsIdle;
3873         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3874                    appData.getMoveList && !reqFlag) {
3875             /* Need to get game history */
3876             ics_getting_history = H_REQUESTED;
3877             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3878             SendToICS(str);
3879         }
3880         
3881         /* Initially flip the board to have black on the bottom if playing
3882            black or if the ICS flip flag is set, but let the user change
3883            it with the Flip View button. */
3884         flipView = appData.autoFlipView ? 
3885           (newGameMode == IcsPlayingBlack) || ics_flip :
3886           appData.flipView;
3887         
3888         /* Done with values from previous mode; copy in new ones */
3889         gameMode = newGameMode;
3890         ModeHighlight();
3891         ics_gamenum = gamenum;
3892         if (gamenum == gs_gamenum) {
3893             int klen = strlen(gs_kind);
3894             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3895             sprintf(str, "ICS %s", gs_kind);
3896             gameInfo.event = StrSave(str);
3897         } else {
3898             gameInfo.event = StrSave("ICS game");
3899         }
3900         gameInfo.site = StrSave(appData.icsHost);
3901         gameInfo.date = PGNDate();
3902         gameInfo.round = StrSave("-");
3903         gameInfo.white = StrSave(white);
3904         gameInfo.black = StrSave(black);
3905         timeControl = basetime * 60 * 1000;
3906         timeControl_2 = 0;
3907         timeIncrement = increment * 1000;
3908         movesPerSession = 0;
3909         gameInfo.timeControl = TimeControlTagValue();
3910         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3911   if (appData.debugMode) {
3912     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3913     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3914     setbuf(debugFP, NULL);
3915   }
3916
3917         gameInfo.outOfBook = NULL;
3918         
3919         /* Do we have the ratings? */
3920         if (strcmp(player1Name, white) == 0 &&
3921             strcmp(player2Name, black) == 0) {
3922             if (appData.debugMode)
3923               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3924                       player1Rating, player2Rating);
3925             gameInfo.whiteRating = player1Rating;
3926             gameInfo.blackRating = player2Rating;
3927         } else if (strcmp(player2Name, white) == 0 &&
3928                    strcmp(player1Name, black) == 0) {
3929             if (appData.debugMode)
3930               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3931                       player2Rating, player1Rating);
3932             gameInfo.whiteRating = player2Rating;
3933             gameInfo.blackRating = player1Rating;
3934         }
3935         player1Name[0] = player2Name[0] = NULLCHAR;
3936
3937         /* Silence shouts if requested */
3938         if (appData.quietPlay &&
3939             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3940             SendToICS(ics_prefix);
3941             SendToICS("set shout 0\n");
3942         }
3943     }
3944     
3945     /* Deal with midgame name changes */
3946     if (!newGame) {
3947         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3948             if (gameInfo.white) free(gameInfo.white);
3949             gameInfo.white = StrSave(white);
3950         }
3951         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3952             if (gameInfo.black) free(gameInfo.black);
3953             gameInfo.black = StrSave(black);
3954         }
3955     }
3956     
3957     /* Throw away game result if anything actually changes in examine mode */
3958     if (gameMode == IcsExamining && !newGame) {
3959         gameInfo.result = GameUnfinished;
3960         if (gameInfo.resultDetails != NULL) {
3961             free(gameInfo.resultDetails);
3962             gameInfo.resultDetails = NULL;
3963         }
3964     }
3965     
3966     /* In pausing && IcsExamining mode, we ignore boards coming
3967        in if they are in a different variation than we are. */
3968     if (pauseExamInvalid) return;
3969     if (pausing && gameMode == IcsExamining) {
3970         if (moveNum <= pauseExamForwardMostMove) {
3971             pauseExamInvalid = TRUE;
3972             forwardMostMove = pauseExamForwardMostMove;
3973             return;
3974         }
3975     }
3976     
3977   if (appData.debugMode) {
3978     fprintf(debugFP, "load %dx%d board\n", files, ranks);
3979   }
3980     /* Parse the board */
3981     for (k = 0; k < ranks; k++) {
3982       for (j = 0; j < files; j++)
3983         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3984       if(gameInfo.holdingsWidth > 1) {
3985            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3986            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3987       }
3988     }
3989     CopyBoard(boards[moveNum], board);
3990     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3991     if (moveNum == 0) {
3992         startedFromSetupPosition =
3993           !CompareBoards(board, initialPosition);
3994         if(startedFromSetupPosition)
3995             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3996     }
3997
3998     /* [HGM] Set castling rights. Take the outermost Rooks,
3999        to make it also work for FRC opening positions. Note that board12
4000        is really defective for later FRC positions, as it has no way to
4001        indicate which Rook can castle if they are on the same side of King.
4002        For the initial position we grant rights to the outermost Rooks,
4003        and remember thos rights, and we then copy them on positions
4004        later in an FRC game. This means WB might not recognize castlings with
4005        Rooks that have moved back to their original position as illegal,
4006        but in ICS mode that is not its job anyway.
4007     */
4008     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4009     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4010
4011         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4012             if(board[0][i] == WhiteRook) j = i;
4013         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4014         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4015             if(board[0][i] == WhiteRook) j = i;
4016         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4017         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4018             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4019         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4020         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4021             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4022         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4023
4024         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4025         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4026             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4027         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4028             if(board[BOARD_HEIGHT-1][k] == bKing)
4029                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4030         if(gameInfo.variant == VariantTwoKings) {
4031             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4032             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4033             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4034         }
4035     } else { int r;
4036         r = boards[moveNum][CASTLING][0] = initialRights[0];
4037         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4038         r = boards[moveNum][CASTLING][1] = initialRights[1];
4039         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4040         r = boards[moveNum][CASTLING][3] = initialRights[3];
4041         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4042         r = boards[moveNum][CASTLING][4] = initialRights[4];
4043         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4044         /* wildcastle kludge: always assume King has rights */
4045         r = boards[moveNum][CASTLING][2] = initialRights[2];
4046         r = boards[moveNum][CASTLING][5] = initialRights[5];
4047     }
4048     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4049     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4050
4051     
4052     if (ics_getting_history == H_GOT_REQ_HEADER ||
4053         ics_getting_history == H_GOT_UNREQ_HEADER) {
4054         /* This was an initial position from a move list, not
4055            the current position */
4056         return;
4057     }
4058     
4059     /* Update currentMove and known move number limits */
4060     newMove = newGame || moveNum > forwardMostMove;
4061
4062     if (newGame) {
4063         forwardMostMove = backwardMostMove = currentMove = moveNum;
4064         if (gameMode == IcsExamining && moveNum == 0) {
4065           /* Workaround for ICS limitation: we are not told the wild
4066              type when starting to examine a game.  But if we ask for
4067              the move list, the move list header will tell us */
4068             ics_getting_history = H_REQUESTED;
4069             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4070             SendToICS(str);
4071         }
4072     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4073                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4074 #if ZIPPY
4075         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4076         /* [HGM] applied this also to an engine that is silently watching        */
4077         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4078             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4079             gameInfo.variant == currentlyInitializedVariant) {
4080           takeback = forwardMostMove - moveNum;
4081           for (i = 0; i < takeback; i++) {
4082             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4083             SendToProgram("undo\n", &first);
4084           }
4085         }
4086 #endif
4087
4088         forwardMostMove = moveNum;
4089         if (!pausing || currentMove > forwardMostMove)
4090           currentMove = forwardMostMove;
4091     } else {
4092         /* New part of history that is not contiguous with old part */ 
4093         if (pausing && gameMode == IcsExamining) {
4094             pauseExamInvalid = TRUE;
4095             forwardMostMove = pauseExamForwardMostMove;
4096             return;
4097         }
4098         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4099 #if ZIPPY
4100             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4101                 // [HGM] when we will receive the move list we now request, it will be
4102                 // fed to the engine from the first move on. So if the engine is not
4103                 // in the initial position now, bring it there.
4104                 InitChessProgram(&first, 0);
4105             }
4106 #endif
4107             ics_getting_history = H_REQUESTED;
4108             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4109             SendToICS(str);
4110         }
4111         forwardMostMove = backwardMostMove = currentMove = moveNum;
4112     }
4113     
4114     /* Update the clocks */
4115     if (strchr(elapsed_time, '.')) {
4116       /* Time is in ms */
4117       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4118       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4119     } else {
4120       /* Time is in seconds */
4121       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4122       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4123     }
4124       
4125
4126 #if ZIPPY
4127     if (appData.zippyPlay && newGame &&
4128         gameMode != IcsObserving && gameMode != IcsIdle &&
4129         gameMode != IcsExamining)
4130       ZippyFirstBoard(moveNum, basetime, increment);
4131 #endif
4132     
4133     /* Put the move on the move list, first converting
4134        to canonical algebraic form. */
4135     if (moveNum > 0) {
4136   if (appData.debugMode) {
4137     if (appData.debugMode) { int f = forwardMostMove;
4138         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4139                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4140                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4141     }
4142     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4143     fprintf(debugFP, "moveNum = %d\n", moveNum);
4144     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4145     setbuf(debugFP, NULL);
4146   }
4147         if (moveNum <= backwardMostMove) {
4148             /* We don't know what the board looked like before
4149                this move.  Punt. */
4150             strcpy(parseList[moveNum - 1], move_str);
4151             strcat(parseList[moveNum - 1], " ");
4152             strcat(parseList[moveNum - 1], elapsed_time);
4153             moveList[moveNum - 1][0] = NULLCHAR;
4154         } else if (strcmp(move_str, "none") == 0) {
4155             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4156             /* Again, we don't know what the board looked like;
4157                this is really the start of the game. */
4158             parseList[moveNum - 1][0] = NULLCHAR;
4159             moveList[moveNum - 1][0] = NULLCHAR;
4160             backwardMostMove = moveNum;
4161             startedFromSetupPosition = TRUE;
4162             fromX = fromY = toX = toY = -1;
4163         } else {
4164           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4165           //                 So we parse the long-algebraic move string in stead of the SAN move
4166           int valid; char buf[MSG_SIZ], *prom;
4167
4168           // str looks something like "Q/a1-a2"; kill the slash
4169           if(str[1] == '/') 
4170                 sprintf(buf, "%c%s", str[0], str+2);
4171           else  strcpy(buf, str); // might be castling
4172           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4173                 strcat(buf, prom); // long move lacks promo specification!
4174           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4175                 if(appData.debugMode) 
4176                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4177                 strcpy(move_str, buf);
4178           }
4179           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4180                                 &fromX, &fromY, &toX, &toY, &promoChar)
4181                || ParseOneMove(buf, moveNum - 1, &moveType,
4182                                 &fromX, &fromY, &toX, &toY, &promoChar);
4183           // end of long SAN patch
4184           if (valid) {
4185             (void) CoordsToAlgebraic(boards[moveNum - 1],
4186                                      PosFlags(moveNum - 1),
4187                                      fromY, fromX, toY, toX, promoChar,
4188                                      parseList[moveNum-1]);
4189             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4190               case MT_NONE:
4191               case MT_STALEMATE:
4192               default:
4193                 break;
4194               case MT_CHECK:
4195                 if(gameInfo.variant != VariantShogi)
4196                     strcat(parseList[moveNum - 1], "+");
4197                 break;
4198               case MT_CHECKMATE:
4199               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4200                 strcat(parseList[moveNum - 1], "#");
4201                 break;
4202             }
4203             strcat(parseList[moveNum - 1], " ");
4204             strcat(parseList[moveNum - 1], elapsed_time);
4205             /* currentMoveString is set as a side-effect of ParseOneMove */
4206             strcpy(moveList[moveNum - 1], currentMoveString);
4207             strcat(moveList[moveNum - 1], "\n");
4208           } else {
4209             /* Move from ICS was illegal!?  Punt. */
4210   if (appData.debugMode) {
4211     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4212     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4213   }
4214             strcpy(parseList[moveNum - 1], move_str);
4215             strcat(parseList[moveNum - 1], " ");
4216             strcat(parseList[moveNum - 1], elapsed_time);
4217             moveList[moveNum - 1][0] = NULLCHAR;
4218             fromX = fromY = toX = toY = -1;
4219           }
4220         }
4221   if (appData.debugMode) {
4222     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4223     setbuf(debugFP, NULL);
4224   }
4225
4226 #if ZIPPY
4227         /* Send move to chess program (BEFORE animating it). */
4228         if (appData.zippyPlay && !newGame && newMove && 
4229            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4230
4231             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4232                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4233                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4234                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4235                             move_str);
4236                     DisplayError(str, 0);
4237                 } else {
4238                     if (first.sendTime) {
4239                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4240                     }
4241                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4242                     if (firstMove && !bookHit) {
4243                         firstMove = FALSE;
4244                         if (first.useColors) {
4245                           SendToProgram(gameMode == IcsPlayingWhite ?
4246                                         "white\ngo\n" :
4247                                         "black\ngo\n", &first);
4248                         } else {
4249                           SendToProgram("go\n", &first);
4250                         }
4251                         first.maybeThinking = TRUE;
4252                     }
4253                 }
4254             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4255               if (moveList[moveNum - 1][0] == NULLCHAR) {
4256                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4257                 DisplayError(str, 0);
4258               } else {
4259                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4260                 SendMoveToProgram(moveNum - 1, &first);
4261               }
4262             }
4263         }
4264 #endif
4265     }
4266
4267     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4268         /* If move comes from a remote source, animate it.  If it
4269            isn't remote, it will have already been animated. */
4270         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4271             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4272         }
4273         if (!pausing && appData.highlightLastMove) {
4274             SetHighlights(fromX, fromY, toX, toY);
4275         }
4276     }
4277     
4278     /* Start the clocks */
4279     whiteFlag = blackFlag = FALSE;
4280     appData.clockMode = !(basetime == 0 && increment == 0);
4281     if (ticking == 0) {
4282       ics_clock_paused = TRUE;
4283       StopClocks();
4284     } else if (ticking == 1) {
4285       ics_clock_paused = FALSE;
4286     }
4287     if (gameMode == IcsIdle ||
4288         relation == RELATION_OBSERVING_STATIC ||
4289         relation == RELATION_EXAMINING ||
4290         ics_clock_paused)
4291       DisplayBothClocks();
4292     else
4293       StartClocks();
4294     
4295     /* Display opponents and material strengths */
4296     if (gameInfo.variant != VariantBughouse &&
4297         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4298         if (tinyLayout || smallLayout) {
4299             if(gameInfo.variant == VariantNormal)
4300                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4301                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4302                     basetime, increment);
4303             else
4304                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4305                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4306                     basetime, increment, (int) gameInfo.variant);
4307         } else {
4308             if(gameInfo.variant == VariantNormal)
4309                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4310                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4311                     basetime, increment);
4312             else
4313                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4314                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4315                     basetime, increment, VariantName(gameInfo.variant));
4316         }
4317         DisplayTitle(str);
4318   if (appData.debugMode) {
4319     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4320   }
4321     }
4322
4323
4324     /* Display the board */
4325     if (!pausing && !appData.noGUI) {
4326       
4327       if (appData.premove)
4328           if (!gotPremove || 
4329              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4330              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4331               ClearPremoveHighlights();
4332
4333       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4334       DrawPosition(j, boards[currentMove]);
4335
4336       DisplayMove(moveNum - 1);
4337       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4338             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4339               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4340         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4341       }
4342     }
4343
4344     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4345 #if ZIPPY
4346     if(bookHit) { // [HGM] book: simulate book reply
4347         static char bookMove[MSG_SIZ]; // a bit generous?
4348
4349         programStats.nodes = programStats.depth = programStats.time = 
4350         programStats.score = programStats.got_only_move = 0;
4351         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4352
4353         strcpy(bookMove, "move ");
4354         strcat(bookMove, bookHit);
4355         HandleMachineMove(bookMove, &first);
4356     }
4357 #endif
4358 }
4359
4360 void
4361 GetMoveListEvent()
4362 {
4363     char buf[MSG_SIZ];
4364     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4365         ics_getting_history = H_REQUESTED;
4366         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4367         SendToICS(buf);
4368     }
4369 }
4370
4371 void
4372 AnalysisPeriodicEvent(force)
4373      int force;
4374 {
4375     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4376          && !force) || !appData.periodicUpdates)
4377       return;
4378
4379     /* Send . command to Crafty to collect stats */
4380     SendToProgram(".\n", &first);
4381
4382     /* Don't send another until we get a response (this makes
4383        us stop sending to old Crafty's which don't understand
4384        the "." command (sending illegal cmds resets node count & time,
4385        which looks bad)) */
4386     programStats.ok_to_send = 0;
4387 }
4388
4389 void ics_update_width(new_width)
4390         int new_width;
4391 {
4392         ics_printf("set width %d\n", new_width);
4393 }
4394
4395 void
4396 SendMoveToProgram(moveNum, cps)
4397      int moveNum;
4398      ChessProgramState *cps;
4399 {
4400     char buf[MSG_SIZ];
4401
4402     if (cps->useUsermove) {
4403       SendToProgram("usermove ", cps);
4404     }
4405     if (cps->useSAN) {
4406       char *space;
4407       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4408         int len = space - parseList[moveNum];
4409         memcpy(buf, parseList[moveNum], len);
4410         buf[len++] = '\n';
4411         buf[len] = NULLCHAR;
4412       } else {
4413         sprintf(buf, "%s\n", parseList[moveNum]);
4414       }
4415       SendToProgram(buf, cps);
4416     } else {
4417       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4418         AlphaRank(moveList[moveNum], 4);
4419         SendToProgram(moveList[moveNum], cps);
4420         AlphaRank(moveList[moveNum], 4); // and back
4421       } else
4422       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4423        * the engine. It would be nice to have a better way to identify castle 
4424        * moves here. */
4425       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4426                                                                          && cps->useOOCastle) {
4427         int fromX = moveList[moveNum][0] - AAA; 
4428         int fromY = moveList[moveNum][1] - ONE;
4429         int toX = moveList[moveNum][2] - AAA; 
4430         int toY = moveList[moveNum][3] - ONE;
4431         if((boards[moveNum][fromY][fromX] == WhiteKing 
4432             && boards[moveNum][toY][toX] == WhiteRook)
4433            || (boards[moveNum][fromY][fromX] == BlackKing 
4434                && boards[moveNum][toY][toX] == BlackRook)) {
4435           if(toX > fromX) SendToProgram("O-O\n", cps);
4436           else SendToProgram("O-O-O\n", cps);
4437         }
4438         else SendToProgram(moveList[moveNum], cps);
4439       }
4440       else SendToProgram(moveList[moveNum], cps);
4441       /* End of additions by Tord */
4442     }
4443
4444     /* [HGM] setting up the opening has brought engine in force mode! */
4445     /*       Send 'go' if we are in a mode where machine should play. */
4446     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4447         (gameMode == TwoMachinesPlay   ||
4448 #ifdef ZIPPY
4449          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4450 #endif
4451          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4452         SendToProgram("go\n", cps);
4453   if (appData.debugMode) {
4454     fprintf(debugFP, "(extra)\n");
4455   }
4456     }
4457     setboardSpoiledMachineBlack = 0;
4458 }
4459
4460 void
4461 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4462      ChessMove moveType;
4463      int fromX, fromY, toX, toY;
4464 {
4465     char user_move[MSG_SIZ];
4466
4467     switch (moveType) {
4468       default:
4469         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4470                 (int)moveType, fromX, fromY, toX, toY);
4471         DisplayError(user_move + strlen("say "), 0);
4472         break;
4473       case WhiteKingSideCastle:
4474       case BlackKingSideCastle:
4475       case WhiteQueenSideCastleWild:
4476       case BlackQueenSideCastleWild:
4477       /* PUSH Fabien */
4478       case WhiteHSideCastleFR:
4479       case BlackHSideCastleFR:
4480       /* POP Fabien */
4481         sprintf(user_move, "o-o\n");
4482         break;
4483       case WhiteQueenSideCastle:
4484       case BlackQueenSideCastle:
4485       case WhiteKingSideCastleWild:
4486       case BlackKingSideCastleWild:
4487       /* PUSH Fabien */
4488       case WhiteASideCastleFR:
4489       case BlackASideCastleFR:
4490       /* POP Fabien */
4491         sprintf(user_move, "o-o-o\n");
4492         break;
4493       case WhitePromotionQueen:
4494       case BlackPromotionQueen:
4495       case WhitePromotionRook:
4496       case BlackPromotionRook:
4497       case WhitePromotionBishop:
4498       case BlackPromotionBishop:
4499       case WhitePromotionKnight:
4500       case BlackPromotionKnight:
4501       case WhitePromotionKing:
4502       case BlackPromotionKing:
4503       case WhitePromotionChancellor:
4504       case BlackPromotionChancellor:
4505       case WhitePromotionArchbishop:
4506       case BlackPromotionArchbishop:
4507         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4508             sprintf(user_move, "%c%c%c%c=%c\n",
4509                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4510                 PieceToChar(WhiteFerz));
4511         else if(gameInfo.variant == VariantGreat)
4512             sprintf(user_move, "%c%c%c%c=%c\n",
4513                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4514                 PieceToChar(WhiteMan));
4515         else
4516             sprintf(user_move, "%c%c%c%c=%c\n",
4517                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4518                 PieceToChar(PromoPiece(moveType)));
4519         break;
4520       case WhiteDrop:
4521       case BlackDrop:
4522         sprintf(user_move, "%c@%c%c\n",
4523                 ToUpper(PieceToChar((ChessSquare) fromX)),
4524                 AAA + toX, ONE + toY);
4525         break;
4526       case NormalMove:
4527       case WhiteCapturesEnPassant:
4528       case BlackCapturesEnPassant:
4529       case IllegalMove:  /* could be a variant we don't quite understand */
4530         sprintf(user_move, "%c%c%c%c\n",
4531                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4532         break;
4533     }
4534     SendToICS(user_move);
4535     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4536         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4537 }
4538
4539 void
4540 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4541      int rf, ff, rt, ft;
4542      char promoChar;
4543      char move[7];
4544 {
4545     if (rf == DROP_RANK) {
4546         sprintf(move, "%c@%c%c\n",
4547                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4548     } else {
4549         if (promoChar == 'x' || promoChar == NULLCHAR) {
4550             sprintf(move, "%c%c%c%c\n",
4551                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4552         } else {
4553             sprintf(move, "%c%c%c%c%c\n",
4554                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4555         }
4556     }
4557 }
4558
4559 void
4560 ProcessICSInitScript(f)
4561      FILE *f;
4562 {
4563     char buf[MSG_SIZ];
4564
4565     while (fgets(buf, MSG_SIZ, f)) {
4566         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4567     }
4568
4569     fclose(f);
4570 }
4571
4572
4573 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4574 void
4575 AlphaRank(char *move, int n)
4576 {
4577 //    char *p = move, c; int x, y;
4578
4579     if (appData.debugMode) {
4580         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4581     }
4582
4583     if(move[1]=='*' && 
4584        move[2]>='0' && move[2]<='9' &&
4585        move[3]>='a' && move[3]<='x'    ) {
4586         move[1] = '@';
4587         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4588         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4589     } else
4590     if(move[0]>='0' && move[0]<='9' &&
4591        move[1]>='a' && move[1]<='x' &&
4592        move[2]>='0' && move[2]<='9' &&
4593        move[3]>='a' && move[3]<='x'    ) {
4594         /* input move, Shogi -> normal */
4595         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4596         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4597         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4598         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4599     } else
4600     if(move[1]=='@' &&
4601        move[3]>='0' && move[3]<='9' &&
4602        move[2]>='a' && move[2]<='x'    ) {
4603         move[1] = '*';
4604         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4605         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4606     } else
4607     if(
4608        move[0]>='a' && move[0]<='x' &&
4609        move[3]>='0' && move[3]<='9' &&
4610        move[2]>='a' && move[2]<='x'    ) {
4611          /* output move, normal -> Shogi */
4612         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4613         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4614         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4615         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4616         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4617     }
4618     if (appData.debugMode) {
4619         fprintf(debugFP, "   out = '%s'\n", move);
4620     }
4621 }
4622
4623 /* Parser for moves from gnuchess, ICS, or user typein box */
4624 Boolean
4625 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4626      char *move;
4627      int moveNum;
4628      ChessMove *moveType;
4629      int *fromX, *fromY, *toX, *toY;
4630      char *promoChar;
4631 {       
4632     if (appData.debugMode) {
4633         fprintf(debugFP, "move to parse: %s\n", move);
4634     }
4635     *moveType = yylexstr(moveNum, move);
4636
4637     switch (*moveType) {
4638       case WhitePromotionChancellor:
4639       case BlackPromotionChancellor:
4640       case WhitePromotionArchbishop:
4641       case BlackPromotionArchbishop:
4642       case WhitePromotionQueen:
4643       case BlackPromotionQueen:
4644       case WhitePromotionRook:
4645       case BlackPromotionRook:
4646       case WhitePromotionBishop:
4647       case BlackPromotionBishop:
4648       case WhitePromotionKnight:
4649       case BlackPromotionKnight:
4650       case WhitePromotionKing:
4651       case BlackPromotionKing:
4652       case NormalMove:
4653       case WhiteCapturesEnPassant:
4654       case BlackCapturesEnPassant:
4655       case WhiteKingSideCastle:
4656       case WhiteQueenSideCastle:
4657       case BlackKingSideCastle:
4658       case BlackQueenSideCastle:
4659       case WhiteKingSideCastleWild:
4660       case WhiteQueenSideCastleWild:
4661       case BlackKingSideCastleWild:
4662       case BlackQueenSideCastleWild:
4663       /* Code added by Tord: */
4664       case WhiteHSideCastleFR:
4665       case WhiteASideCastleFR:
4666       case BlackHSideCastleFR:
4667       case BlackASideCastleFR:
4668       /* End of code added by Tord */
4669       case IllegalMove:         /* bug or odd chess variant */
4670         *fromX = currentMoveString[0] - AAA;
4671         *fromY = currentMoveString[1] - ONE;
4672         *toX = currentMoveString[2] - AAA;
4673         *toY = currentMoveString[3] - ONE;
4674         *promoChar = currentMoveString[4];
4675         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4676             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4677     if (appData.debugMode) {
4678         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4679     }
4680             *fromX = *fromY = *toX = *toY = 0;
4681             return FALSE;
4682         }
4683         if (appData.testLegality) {
4684           return (*moveType != IllegalMove);
4685         } else {
4686           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4687                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4688         }
4689
4690       case WhiteDrop:
4691       case BlackDrop:
4692         *fromX = *moveType == WhiteDrop ?
4693           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4694           (int) CharToPiece(ToLower(currentMoveString[0]));
4695         *fromY = DROP_RANK;
4696         *toX = currentMoveString[2] - AAA;
4697         *toY = currentMoveString[3] - ONE;
4698         *promoChar = NULLCHAR;
4699         return TRUE;
4700
4701       case AmbiguousMove:
4702       case ImpossibleMove:
4703       case (ChessMove) 0:       /* end of file */
4704       case ElapsedTime:
4705       case Comment:
4706       case PGNTag:
4707       case NAG:
4708       case WhiteWins:
4709       case BlackWins:
4710       case GameIsDrawn:
4711       default:
4712     if (appData.debugMode) {
4713         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4714     }
4715         /* bug? */
4716         *fromX = *fromY = *toX = *toY = 0;
4717         *promoChar = NULLCHAR;
4718         return FALSE;
4719     }
4720 }
4721
4722
4723 void
4724 ParsePV(char *pv)
4725 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4726   int fromX, fromY, toX, toY; char promoChar;
4727   ChessMove moveType;
4728   Boolean valid;
4729   int nr = 0;
4730
4731   endPV = forwardMostMove;
4732   do {
4733     while(*pv == ' ') pv++;
4734     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4735     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4736 if(appData.debugMode){
4737 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4738 }
4739     if(!valid && nr == 0 &&
4740        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4741         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4742     }
4743     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4744     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4745     nr++;
4746     if(endPV+1 > framePtr) break; // no space, truncate
4747     if(!valid) break;
4748     endPV++;
4749     CopyBoard(boards[endPV], boards[endPV-1]);
4750     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4751     moveList[endPV-1][0] = fromX + AAA;
4752     moveList[endPV-1][1] = fromY + ONE;
4753     moveList[endPV-1][2] = toX + AAA;
4754     moveList[endPV-1][3] = toY + ONE;
4755     parseList[endPV-1][0] = NULLCHAR;
4756   } while(valid);
4757   currentMove = endPV;
4758   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4759   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4760                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4761   DrawPosition(TRUE, boards[currentMove]);
4762 }
4763
4764 static int lastX, lastY;
4765
4766 Boolean
4767 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4768 {
4769         int startPV;
4770
4771         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4772         lastX = x; lastY = y;
4773         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4774         startPV = index;
4775       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4776       index = startPV;
4777         while(buf[index] && buf[index] != '\n') index++;
4778         buf[index] = 0;
4779         ParsePV(buf+startPV);
4780         *start = startPV; *end = index-1;
4781         return TRUE;
4782 }
4783
4784 Boolean
4785 LoadPV(int x, int y)
4786 { // called on right mouse click to load PV
4787   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4788   lastX = x; lastY = y;
4789   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4790   return TRUE;
4791 }
4792
4793 void
4794 UnLoadPV()
4795 {
4796   if(endPV < 0) return;
4797   endPV = -1;
4798   currentMove = forwardMostMove;
4799   ClearPremoveHighlights();
4800   DrawPosition(TRUE, boards[currentMove]);
4801 }
4802
4803 void
4804 MovePV(int x, int y, int h)
4805 { // step through PV based on mouse coordinates (called on mouse move)
4806   int margin = h>>3, step = 0;
4807
4808   if(endPV < 0) return;
4809   // we must somehow check if right button is still down (might be released off board!)
4810   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4811   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4812   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4813   if(!step) return;
4814   lastX = x; lastY = y;
4815   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4816   currentMove += step;
4817   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4818   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4819                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4820   DrawPosition(FALSE, boards[currentMove]);
4821 }
4822
4823
4824 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4825 // All positions will have equal probability, but the current method will not provide a unique
4826 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4827 #define DARK 1
4828 #define LITE 2
4829 #define ANY 3
4830
4831 int squaresLeft[4];
4832 int piecesLeft[(int)BlackPawn];
4833 int seed, nrOfShuffles;
4834
4835 void GetPositionNumber()
4836 {       // sets global variable seed
4837         int i;
4838
4839         seed = appData.defaultFrcPosition;
4840         if(seed < 0) { // randomize based on time for negative FRC position numbers
4841                 for(i=0; i<50; i++) seed += random();
4842                 seed = random() ^ random() >> 8 ^ random() << 8;
4843                 if(seed<0) seed = -seed;
4844         }
4845 }
4846
4847 int put(Board board, int pieceType, int rank, int n, int shade)
4848 // put the piece on the (n-1)-th empty squares of the given shade
4849 {
4850         int i;
4851
4852         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4853                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4854                         board[rank][i] = (ChessSquare) pieceType;
4855                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4856                         squaresLeft[ANY]--;
4857                         piecesLeft[pieceType]--; 
4858                         return i;
4859                 }
4860         }
4861         return -1;
4862 }
4863
4864
4865 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4866 // calculate where the next piece goes, (any empty square), and put it there
4867 {
4868         int i;
4869
4870         i = seed % squaresLeft[shade];
4871         nrOfShuffles *= squaresLeft[shade];
4872         seed /= squaresLeft[shade];
4873         put(board, pieceType, rank, i, shade);
4874 }
4875
4876 void AddTwoPieces(Board board, int pieceType, int rank)
4877 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4878 {
4879         int i, n=squaresLeft[ANY], j=n-1, k;
4880
4881         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4882         i = seed % k;  // pick one
4883         nrOfShuffles *= k;
4884         seed /= k;
4885         while(i >= j) i -= j--;
4886         j = n - 1 - j; i += j;
4887         put(board, pieceType, rank, j, ANY);
4888         put(board, pieceType, rank, i, ANY);
4889 }
4890
4891 void SetUpShuffle(Board board, int number)
4892 {
4893         int i, p, first=1;
4894
4895         GetPositionNumber(); nrOfShuffles = 1;
4896
4897         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4898         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
4899         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4900
4901         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4902
4903         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4904             p = (int) board[0][i];
4905             if(p < (int) BlackPawn) piecesLeft[p] ++;
4906             board[0][i] = EmptySquare;
4907         }
4908
4909         if(PosFlags(0) & F_ALL_CASTLE_OK) {
4910             // shuffles restricted to allow normal castling put KRR first
4911             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4912                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4913             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4914                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4915             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4916                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4917             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4918                 put(board, WhiteRook, 0, 0, ANY);
4919             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4920         }
4921
4922         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4923             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4924             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4925                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4926                 while(piecesLeft[p] >= 2) {
4927                     AddOnePiece(board, p, 0, LITE);
4928                     AddOnePiece(board, p, 0, DARK);
4929                 }
4930                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4931             }
4932
4933         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4934             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4935             // but we leave King and Rooks for last, to possibly obey FRC restriction
4936             if(p == (int)WhiteRook) continue;
4937             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4938             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
4939         }
4940
4941         // now everything is placed, except perhaps King (Unicorn) and Rooks
4942
4943         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4944             // Last King gets castling rights
4945             while(piecesLeft[(int)WhiteUnicorn]) {
4946                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4947                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4948             }
4949
4950             while(piecesLeft[(int)WhiteKing]) {
4951                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4952                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
4953             }
4954
4955
4956         } else {
4957             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
4958             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4959         }
4960
4961         // Only Rooks can be left; simply place them all
4962         while(piecesLeft[(int)WhiteRook]) {
4963                 i = put(board, WhiteRook, 0, 0, ANY);
4964                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4965                         if(first) {
4966                                 first=0;
4967                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
4968                         }
4969                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
4970                 }
4971         }
4972         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4973             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4974         }
4975
4976         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4977 }
4978
4979 int SetCharTable( char *table, const char * map )
4980 /* [HGM] moved here from winboard.c because of its general usefulness */
4981 /*       Basically a safe strcpy that uses the last character as King */
4982 {
4983     int result = FALSE; int NrPieces;
4984
4985     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
4986                     && NrPieces >= 12 && !(NrPieces&1)) {
4987         int i; /* [HGM] Accept even length from 12 to 34 */
4988
4989         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4990         for( i=0; i<NrPieces/2-1; i++ ) {
4991             table[i] = map[i];
4992             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4993         }
4994         table[(int) WhiteKing]  = map[NrPieces/2-1];
4995         table[(int) BlackKing]  = map[NrPieces-1];
4996
4997         result = TRUE;
4998     }
4999
5000     return result;
5001 }
5002
5003 void Prelude(Board board)
5004 {       // [HGM] superchess: random selection of exo-pieces
5005         int i, j, k; ChessSquare p; 
5006         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5007
5008         GetPositionNumber(); // use FRC position number
5009
5010         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5011             SetCharTable(pieceToChar, appData.pieceToCharTable);
5012             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5013                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5014         }
5015
5016         j = seed%4;                 seed /= 4; 
5017         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5018         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5019         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5020         j = seed%3 + (seed%3 >= j); seed /= 3; 
5021         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5022         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5023         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5024         j = seed%3;                 seed /= 3; 
5025         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5026         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5027         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5028         j = seed%2 + (seed%2 >= j); seed /= 2; 
5029         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5030         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5031         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5032         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5033         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5034         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5035         put(board, exoPieces[0],    0, 0, ANY);
5036         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5037 }
5038
5039 void
5040 InitPosition(redraw)
5041      int redraw;
5042 {
5043     ChessSquare (* pieces)[BOARD_FILES];
5044     int i, j, pawnRow, overrule,
5045     oldx = gameInfo.boardWidth,
5046     oldy = gameInfo.boardHeight,
5047     oldh = gameInfo.holdingsWidth,
5048     oldv = gameInfo.variant;
5049
5050     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5051
5052     /* [AS] Initialize pv info list [HGM] and game status */
5053     {
5054         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5055             pvInfoList[i].depth = 0;
5056             boards[i][EP_STATUS] = EP_NONE;
5057             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5058         }
5059
5060         initialRulePlies = 0; /* 50-move counter start */
5061
5062         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5063         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5064     }
5065
5066     
5067     /* [HGM] logic here is completely changed. In stead of full positions */
5068     /* the initialized data only consist of the two backranks. The switch */
5069     /* selects which one we will use, which is than copied to the Board   */
5070     /* initialPosition, which for the rest is initialized by Pawns and    */
5071     /* empty squares. This initial position is then copied to boards[0],  */
5072     /* possibly after shuffling, so that it remains available.            */
5073
5074     gameInfo.holdingsWidth = 0; /* default board sizes */
5075     gameInfo.boardWidth    = 8;
5076     gameInfo.boardHeight   = 8;
5077     gameInfo.holdingsSize  = 0;
5078     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5079     for(i=0; i<BOARD_FILES-2; i++)
5080       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5081     initialPosition[EP_STATUS] = EP_NONE;
5082     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5083
5084     switch (gameInfo.variant) {
5085     case VariantFischeRandom:
5086       shuffleOpenings = TRUE;
5087     default:
5088       pieces = FIDEArray;
5089       break;
5090     case VariantShatranj:
5091       pieces = ShatranjArray;
5092       nrCastlingRights = 0;
5093       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5094       break;
5095     case VariantMakruk:
5096       pieces = makrukArray;
5097       nrCastlingRights = 0;
5098       startedFromSetupPosition = TRUE;
5099       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5100       break;
5101     case VariantTwoKings:
5102       pieces = twoKingsArray;
5103       break;
5104     case VariantCapaRandom:
5105       shuffleOpenings = TRUE;
5106     case VariantCapablanca:
5107       pieces = CapablancaArray;
5108       gameInfo.boardWidth = 10;
5109       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5110       break;
5111     case VariantGothic:
5112       pieces = GothicArray;
5113       gameInfo.boardWidth = 10;
5114       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5115       break;
5116     case VariantJanus:
5117       pieces = JanusArray;
5118       gameInfo.boardWidth = 10;
5119       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5120       nrCastlingRights = 6;
5121         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5122         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5123         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5124         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5125         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5126         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5127       break;
5128     case VariantFalcon:
5129       pieces = FalconArray;
5130       gameInfo.boardWidth = 10;
5131       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5132       break;
5133     case VariantXiangqi:
5134       pieces = XiangqiArray;
5135       gameInfo.boardWidth  = 9;
5136       gameInfo.boardHeight = 10;
5137       nrCastlingRights = 0;
5138       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5139       break;
5140     case VariantShogi:
5141       pieces = ShogiArray;
5142       gameInfo.boardWidth  = 9;
5143       gameInfo.boardHeight = 9;
5144       gameInfo.holdingsSize = 7;
5145       nrCastlingRights = 0;
5146       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5147       break;
5148     case VariantCourier:
5149       pieces = CourierArray;
5150       gameInfo.boardWidth  = 12;
5151       nrCastlingRights = 0;
5152       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5153       break;
5154     case VariantKnightmate:
5155       pieces = KnightmateArray;
5156       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5157       break;
5158     case VariantFairy:
5159       pieces = fairyArray;
5160       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5161       break;
5162     case VariantGreat:
5163       pieces = GreatArray;
5164       gameInfo.boardWidth = 10;
5165       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5166       gameInfo.holdingsSize = 8;
5167       break;
5168     case VariantSuper:
5169       pieces = FIDEArray;
5170       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5171       gameInfo.holdingsSize = 8;
5172       startedFromSetupPosition = TRUE;
5173       break;
5174     case VariantCrazyhouse:
5175     case VariantBughouse:
5176       pieces = FIDEArray;
5177       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5178       gameInfo.holdingsSize = 5;
5179       break;
5180     case VariantWildCastle:
5181       pieces = FIDEArray;
5182       /* !!?shuffle with kings guaranteed to be on d or e file */
5183       shuffleOpenings = 1;
5184       break;
5185     case VariantNoCastle:
5186       pieces = FIDEArray;
5187       nrCastlingRights = 0;
5188       /* !!?unconstrained back-rank shuffle */
5189       shuffleOpenings = 1;
5190       break;
5191     }
5192
5193     overrule = 0;
5194     if(appData.NrFiles >= 0) {
5195         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5196         gameInfo.boardWidth = appData.NrFiles;
5197     }
5198     if(appData.NrRanks >= 0) {
5199         gameInfo.boardHeight = appData.NrRanks;
5200     }
5201     if(appData.holdingsSize >= 0) {
5202         i = appData.holdingsSize;
5203         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5204         gameInfo.holdingsSize = i;
5205     }
5206     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5207     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5208         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5209
5210     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5211     if(pawnRow < 1) pawnRow = 1;
5212     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5213
5214     /* User pieceToChar list overrules defaults */
5215     if(appData.pieceToCharTable != NULL)
5216         SetCharTable(pieceToChar, appData.pieceToCharTable);
5217
5218     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5219
5220         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5221             s = (ChessSquare) 0; /* account holding counts in guard band */
5222         for( i=0; i<BOARD_HEIGHT; i++ )
5223             initialPosition[i][j] = s;
5224
5225         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5226         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5227         initialPosition[pawnRow][j] = WhitePawn;
5228         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5229         if(gameInfo.variant == VariantXiangqi) {
5230             if(j&1) {
5231                 initialPosition[pawnRow][j] = 
5232                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5233                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5234                    initialPosition[2][j] = WhiteCannon;
5235                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5236                 }
5237             }
5238         }
5239         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5240     }
5241     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5242
5243             j=BOARD_LEFT+1;
5244             initialPosition[1][j] = WhiteBishop;
5245             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5246             j=BOARD_RGHT-2;
5247             initialPosition[1][j] = WhiteRook;
5248             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5249     }
5250
5251     if( nrCastlingRights == -1) {
5252         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5253         /*       This sets default castling rights from none to normal corners   */
5254         /* Variants with other castling rights must set them themselves above    */
5255         nrCastlingRights = 6;
5256        
5257         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5258         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5259         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5260         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5261         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5262         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5263      }
5264
5265      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5266      if(gameInfo.variant == VariantGreat) { // promotion commoners
5267         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5268         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5269         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5270         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5271      }
5272   if (appData.debugMode) {
5273     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5274   }
5275     if(shuffleOpenings) {
5276         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5277         startedFromSetupPosition = TRUE;
5278     }
5279     if(startedFromPositionFile) {
5280       /* [HGM] loadPos: use PositionFile for every new game */
5281       CopyBoard(initialPosition, filePosition);
5282       for(i=0; i<nrCastlingRights; i++)
5283           initialRights[i] = filePosition[CASTLING][i];
5284       startedFromSetupPosition = TRUE;
5285     }
5286
5287     CopyBoard(boards[0], initialPosition);
5288
5289     if(oldx != gameInfo.boardWidth ||
5290        oldy != gameInfo.boardHeight ||
5291        oldh != gameInfo.holdingsWidth
5292 #ifdef GOTHIC
5293        || oldv == VariantGothic ||        // For licensing popups
5294        gameInfo.variant == VariantGothic
5295 #endif
5296 #ifdef FALCON
5297        || oldv == VariantFalcon ||
5298        gameInfo.variant == VariantFalcon
5299 #endif
5300                                          )
5301             InitDrawingSizes(-2 ,0);
5302
5303     if (redraw)
5304       DrawPosition(TRUE, boards[currentMove]);
5305 }
5306
5307 void
5308 SendBoard(cps, moveNum)
5309      ChessProgramState *cps;
5310      int moveNum;
5311 {
5312     char message[MSG_SIZ];
5313     
5314     if (cps->useSetboard) {
5315       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5316       sprintf(message, "setboard %s\n", fen);
5317       SendToProgram(message, cps);
5318       free(fen);
5319
5320     } else {
5321       ChessSquare *bp;
5322       int i, j;
5323       /* Kludge to set black to move, avoiding the troublesome and now
5324        * deprecated "black" command.
5325        */
5326       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5327
5328       SendToProgram("edit\n", cps);
5329       SendToProgram("#\n", cps);
5330       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5331         bp = &boards[moveNum][i][BOARD_LEFT];
5332         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5333           if ((int) *bp < (int) BlackPawn) {
5334             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5335                     AAA + j, ONE + i);
5336             if(message[0] == '+' || message[0] == '~') {
5337                 sprintf(message, "%c%c%c+\n",
5338                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5339                         AAA + j, ONE + i);
5340             }
5341             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5342                 message[1] = BOARD_RGHT   - 1 - j + '1';
5343                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5344             }
5345             SendToProgram(message, cps);
5346           }
5347         }
5348       }
5349     
5350       SendToProgram("c\n", cps);
5351       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5352         bp = &boards[moveNum][i][BOARD_LEFT];
5353         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5354           if (((int) *bp != (int) EmptySquare)
5355               && ((int) *bp >= (int) BlackPawn)) {
5356             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5357                     AAA + j, ONE + i);
5358             if(message[0] == '+' || message[0] == '~') {
5359                 sprintf(message, "%c%c%c+\n",
5360                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5361                         AAA + j, ONE + i);
5362             }
5363             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5364                 message[1] = BOARD_RGHT   - 1 - j + '1';
5365                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5366             }
5367             SendToProgram(message, cps);
5368           }
5369         }
5370       }
5371     
5372       SendToProgram(".\n", cps);
5373     }
5374     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5375 }
5376
5377 static int autoQueen; // [HGM] oneclick
5378
5379 int
5380 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5381 {
5382     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5383     /* [HGM] add Shogi promotions */
5384     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5385     ChessSquare piece;
5386     ChessMove moveType;
5387     Boolean premove;
5388
5389     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5390     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5391
5392     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5393       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5394         return FALSE;
5395
5396     piece = boards[currentMove][fromY][fromX];
5397     if(gameInfo.variant == VariantShogi) {
5398         promotionZoneSize = 3;
5399         highestPromotingPiece = (int)WhiteFerz;
5400     } else if(gameInfo.variant == VariantMakruk) {
5401         promotionZoneSize = 3;
5402     }
5403
5404     // next weed out all moves that do not touch the promotion zone at all
5405     if((int)piece >= BlackPawn) {
5406         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5407              return FALSE;
5408         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5409     } else {
5410         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5411            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5412     }
5413
5414     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5415
5416     // weed out mandatory Shogi promotions
5417     if(gameInfo.variant == VariantShogi) {
5418         if(piece >= BlackPawn) {
5419             if(toY == 0 && piece == BlackPawn ||
5420                toY == 0 && piece == BlackQueen ||
5421                toY <= 1 && piece == BlackKnight) {
5422                 *promoChoice = '+';
5423                 return FALSE;
5424             }
5425         } else {
5426             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5427                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5428                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5429                 *promoChoice = '+';
5430                 return FALSE;
5431             }
5432         }
5433     }
5434
5435     // weed out obviously illegal Pawn moves
5436     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5437         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5438         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5439         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5440         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5441         // note we are not allowed to test for valid (non-)capture, due to premove
5442     }
5443
5444     // we either have a choice what to promote to, or (in Shogi) whether to promote
5445     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5446         *promoChoice = PieceToChar(BlackFerz);  // no choice
5447         return FALSE;
5448     }
5449     if(autoQueen) { // predetermined
5450         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5451              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5452         else *promoChoice = PieceToChar(BlackQueen);
5453         return FALSE;
5454     }
5455
5456     // suppress promotion popup on illegal moves that are not premoves
5457     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5458               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5459     if(appData.testLegality && !premove) {
5460         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5461                         fromY, fromX, toY, toX, NULLCHAR);
5462         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5463            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5464             return FALSE;
5465     }
5466
5467     return TRUE;
5468 }
5469
5470 int
5471 InPalace(row, column)
5472      int row, column;
5473 {   /* [HGM] for Xiangqi */
5474     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5475          column < (BOARD_WIDTH + 4)/2 &&
5476          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5477     return FALSE;
5478 }
5479
5480 int
5481 PieceForSquare (x, y)
5482      int x;
5483      int y;
5484 {
5485   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5486      return -1;
5487   else
5488      return boards[currentMove][y][x];
5489 }
5490
5491 int
5492 OKToStartUserMove(x, y)
5493      int x, y;
5494 {
5495     ChessSquare from_piece;
5496     int white_piece;
5497
5498     if (matchMode) return FALSE;
5499     if (gameMode == EditPosition) return TRUE;
5500
5501     if (x >= 0 && y >= 0)
5502       from_piece = boards[currentMove][y][x];
5503     else
5504       from_piece = EmptySquare;
5505
5506     if (from_piece == EmptySquare) return FALSE;
5507
5508     white_piece = (int)from_piece >= (int)WhitePawn &&
5509       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5510
5511     switch (gameMode) {
5512       case PlayFromGameFile:
5513       case AnalyzeFile:
5514       case TwoMachinesPlay:
5515       case EndOfGame:
5516         return FALSE;
5517
5518       case IcsObserving:
5519       case IcsIdle:
5520         return FALSE;
5521
5522       case MachinePlaysWhite:
5523       case IcsPlayingBlack:
5524         if (appData.zippyPlay) return FALSE;
5525         if (white_piece) {
5526             DisplayMoveError(_("You are playing Black"));
5527             return FALSE;
5528         }
5529         break;
5530
5531       case MachinePlaysBlack:
5532       case IcsPlayingWhite:
5533         if (appData.zippyPlay) return FALSE;
5534         if (!white_piece) {
5535             DisplayMoveError(_("You are playing White"));
5536             return FALSE;
5537         }
5538         break;
5539
5540       case EditGame:
5541         if (!white_piece && WhiteOnMove(currentMove)) {
5542             DisplayMoveError(_("It is White's turn"));
5543             return FALSE;
5544         }           
5545         if (white_piece && !WhiteOnMove(currentMove)) {
5546             DisplayMoveError(_("It is Black's turn"));
5547             return FALSE;
5548         }           
5549         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5550             /* Editing correspondence game history */
5551             /* Could disallow this or prompt for confirmation */
5552             cmailOldMove = -1;
5553         }
5554         break;
5555
5556       case BeginningOfGame:
5557         if (appData.icsActive) return FALSE;
5558         if (!appData.noChessProgram) {
5559             if (!white_piece) {
5560                 DisplayMoveError(_("You are playing White"));
5561                 return FALSE;
5562             }
5563         }
5564         break;
5565         
5566       case Training:
5567         if (!white_piece && WhiteOnMove(currentMove)) {
5568             DisplayMoveError(_("It is White's turn"));
5569             return FALSE;
5570         }           
5571         if (white_piece && !WhiteOnMove(currentMove)) {
5572             DisplayMoveError(_("It is Black's turn"));
5573             return FALSE;
5574         }           
5575         break;
5576
5577       default:
5578       case IcsExamining:
5579         break;
5580     }
5581     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5582         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5583         && gameMode != AnalyzeFile && gameMode != Training) {
5584         DisplayMoveError(_("Displayed position is not current"));
5585         return FALSE;
5586     }
5587     return TRUE;
5588 }
5589
5590 Boolean
5591 OnlyMove(int *x, int *y, Boolean captures) {
5592     DisambiguateClosure cl;
5593     if (appData.zippyPlay) return FALSE;
5594     switch(gameMode) {
5595       case MachinePlaysBlack:
5596       case IcsPlayingWhite:
5597       case BeginningOfGame:
5598         if(!WhiteOnMove(currentMove)) return FALSE;
5599         break;
5600       case MachinePlaysWhite:
5601       case IcsPlayingBlack:
5602         if(WhiteOnMove(currentMove)) return FALSE;
5603         break;
5604       default:
5605         return FALSE;
5606     }
5607     cl.pieceIn = EmptySquare; 
5608     cl.rfIn = *y;
5609     cl.ffIn = *x;
5610     cl.rtIn = -1;
5611     cl.ftIn = -1;
5612     cl.promoCharIn = NULLCHAR;
5613     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5614     if( cl.kind == NormalMove ||
5615         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5616         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5617         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5618         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5619       fromX = cl.ff;
5620       fromY = cl.rf;
5621       *x = cl.ft;
5622       *y = cl.rt;
5623       return TRUE;
5624     }
5625     if(cl.kind != ImpossibleMove) return FALSE;
5626     cl.pieceIn = EmptySquare;
5627     cl.rfIn = -1;
5628     cl.ffIn = -1;
5629     cl.rtIn = *y;
5630     cl.ftIn = *x;
5631     cl.promoCharIn = NULLCHAR;
5632     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5633     if( cl.kind == NormalMove ||
5634         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5635         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5636         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5637         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5638       fromX = cl.ff;
5639       fromY = cl.rf;
5640       *x = cl.ft;
5641       *y = cl.rt;
5642       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5643       return TRUE;
5644     }
5645     return FALSE;
5646 }
5647
5648 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5649 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5650 int lastLoadGameUseList = FALSE;
5651 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5652 ChessMove lastLoadGameStart = (ChessMove) 0;
5653
5654 ChessMove
5655 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5656      int fromX, fromY, toX, toY;
5657      int promoChar;
5658      Boolean captureOwn;
5659 {
5660     ChessMove moveType;
5661     ChessSquare pdown, pup;
5662
5663     /* Check if the user is playing in turn.  This is complicated because we
5664        let the user "pick up" a piece before it is his turn.  So the piece he
5665        tried to pick up may have been captured by the time he puts it down!
5666        Therefore we use the color the user is supposed to be playing in this
5667        test, not the color of the piece that is currently on the starting
5668        square---except in EditGame mode, where the user is playing both
5669        sides; fortunately there the capture race can't happen.  (It can
5670        now happen in IcsExamining mode, but that's just too bad.  The user
5671        will get a somewhat confusing message in that case.)
5672        */
5673
5674     switch (gameMode) {
5675       case PlayFromGameFile:
5676       case AnalyzeFile:
5677       case TwoMachinesPlay:
5678       case EndOfGame:
5679       case IcsObserving:
5680       case IcsIdle:
5681         /* We switched into a game mode where moves are not accepted,
5682            perhaps while the mouse button was down. */
5683         return ImpossibleMove;
5684
5685       case MachinePlaysWhite:
5686         /* User is moving for Black */
5687         if (WhiteOnMove(currentMove)) {
5688             DisplayMoveError(_("It is White's turn"));
5689             return ImpossibleMove;
5690         }
5691         break;
5692
5693       case MachinePlaysBlack:
5694         /* User is moving for White */
5695         if (!WhiteOnMove(currentMove)) {
5696             DisplayMoveError(_("It is Black's turn"));
5697             return ImpossibleMove;
5698         }
5699         break;
5700
5701       case EditGame:
5702       case IcsExamining:
5703       case BeginningOfGame:
5704       case AnalyzeMode:
5705       case Training:
5706         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5707             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5708             /* User is moving for Black */
5709             if (WhiteOnMove(currentMove)) {
5710                 DisplayMoveError(_("It is White's turn"));
5711                 return ImpossibleMove;
5712             }
5713         } else {
5714             /* User is moving for White */
5715             if (!WhiteOnMove(currentMove)) {
5716                 DisplayMoveError(_("It is Black's turn"));
5717                 return ImpossibleMove;
5718             }
5719         }
5720         break;
5721
5722       case IcsPlayingBlack:
5723         /* User is moving for Black */
5724         if (WhiteOnMove(currentMove)) {
5725             if (!appData.premove) {
5726                 DisplayMoveError(_("It is White's turn"));
5727             } else if (toX >= 0 && toY >= 0) {
5728                 premoveToX = toX;
5729                 premoveToY = toY;
5730                 premoveFromX = fromX;
5731                 premoveFromY = fromY;
5732                 premovePromoChar = promoChar;
5733                 gotPremove = 1;
5734                 if (appData.debugMode) 
5735                     fprintf(debugFP, "Got premove: fromX %d,"
5736                             "fromY %d, toX %d, toY %d\n",
5737                             fromX, fromY, toX, toY);
5738             }
5739             return ImpossibleMove;
5740         }
5741         break;
5742
5743       case IcsPlayingWhite:
5744         /* User is moving for White */
5745         if (!WhiteOnMove(currentMove)) {
5746             if (!appData.premove) {
5747                 DisplayMoveError(_("It is Black's turn"));
5748             } else if (toX >= 0 && toY >= 0) {
5749                 premoveToX = toX;
5750                 premoveToY = toY;
5751                 premoveFromX = fromX;
5752                 premoveFromY = fromY;
5753                 premovePromoChar = promoChar;
5754                 gotPremove = 1;
5755                 if (appData.debugMode) 
5756                     fprintf(debugFP, "Got premove: fromX %d,"
5757                             "fromY %d, toX %d, toY %d\n",
5758                             fromX, fromY, toX, toY);
5759             }
5760             return ImpossibleMove;
5761         }
5762         break;
5763
5764       default:
5765         break;
5766
5767       case EditPosition:
5768         /* EditPosition, empty square, or different color piece;
5769            click-click move is possible */
5770         if (toX == -2 || toY == -2) {
5771             boards[0][fromY][fromX] = EmptySquare;
5772             return AmbiguousMove;
5773         } else if (toX >= 0 && toY >= 0) {
5774             boards[0][toY][toX] = boards[0][fromY][fromX];
5775             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5776                 if(boards[0][fromY][0] != EmptySquare) {
5777                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5778                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5779                 }
5780             } else
5781             if(fromX == BOARD_RGHT+1) {
5782                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5783                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5784                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5785                 }
5786             } else
5787             boards[0][fromY][fromX] = EmptySquare;
5788             return AmbiguousMove;
5789         }
5790         return ImpossibleMove;
5791     }
5792
5793     if(toX < 0 || toY < 0) return ImpossibleMove;
5794     pdown = boards[currentMove][fromY][fromX];
5795     pup = boards[currentMove][toY][toX];
5796
5797     /* [HGM] If move started in holdings, it means a drop */
5798     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5799          if( pup != EmptySquare ) return ImpossibleMove;
5800          if(appData.testLegality) {
5801              /* it would be more logical if LegalityTest() also figured out
5802               * which drops are legal. For now we forbid pawns on back rank.
5803               * Shogi is on its own here...
5804               */
5805              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5806                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5807                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5808          }
5809          return WhiteDrop; /* Not needed to specify white or black yet */
5810     }
5811
5812     /* [HGM] always test for legality, to get promotion info */
5813     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5814                                          fromY, fromX, toY, toX, promoChar);
5815     /* [HGM] but possibly ignore an IllegalMove result */
5816     if (appData.testLegality) {
5817         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5818             DisplayMoveError(_("Illegal move"));
5819             return ImpossibleMove;
5820         }
5821     }
5822
5823     return moveType;
5824     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5825        function is made into one that returns an OK move type if FinishMove
5826        should be called. This to give the calling driver routine the
5827        opportunity to finish the userMove input with a promotion popup,
5828        without bothering the user with this for invalid or illegal moves */
5829
5830 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5831 }
5832
5833 /* Common tail of UserMoveEvent and DropMenuEvent */
5834 int
5835 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5836      ChessMove moveType;
5837      int fromX, fromY, toX, toY;
5838      /*char*/int promoChar;
5839 {
5840     char *bookHit = 0;
5841
5842     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5843         // [HGM] superchess: suppress promotions to non-available piece
5844         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5845         if(WhiteOnMove(currentMove)) {
5846             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5847         } else {
5848             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5849         }
5850     }
5851
5852     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5853        move type in caller when we know the move is a legal promotion */
5854     if(moveType == NormalMove && promoChar)
5855         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5856
5857     /* [HGM] convert drag-and-drop piece drops to standard form */
5858     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5859          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5860            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5861                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5862            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5863            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5864            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5865            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5866          fromY = DROP_RANK;
5867     }
5868
5869     /* [HGM] <popupFix> The following if has been moved here from
5870        UserMoveEvent(). Because it seemed to belong here (why not allow
5871        piece drops in training games?), and because it can only be
5872        performed after it is known to what we promote. */
5873     if (gameMode == Training) {
5874       /* compare the move played on the board to the next move in the
5875        * game. If they match, display the move and the opponent's response. 
5876        * If they don't match, display an error message.
5877        */
5878       int saveAnimate;
5879       Board testBoard;
5880       CopyBoard(testBoard, boards[currentMove]);
5881       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5882
5883       if (CompareBoards(testBoard, boards[currentMove+1])) {
5884         ForwardInner(currentMove+1);
5885
5886         /* Autoplay the opponent's response.
5887          * if appData.animate was TRUE when Training mode was entered,
5888          * the response will be animated.
5889          */
5890         saveAnimate = appData.animate;
5891         appData.animate = animateTraining;
5892         ForwardInner(currentMove+1);
5893         appData.animate = saveAnimate;
5894
5895         /* check for the end of the game */
5896         if (currentMove >= forwardMostMove) {
5897           gameMode = PlayFromGameFile;
5898           ModeHighlight();
5899           SetTrainingModeOff();
5900           DisplayInformation(_("End of game"));
5901         }
5902       } else {
5903         DisplayError(_("Incorrect move"), 0);
5904       }
5905       return 1;
5906     }
5907
5908   /* Ok, now we know that the move is good, so we can kill
5909      the previous line in Analysis Mode */
5910   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
5911                                 && currentMove < forwardMostMove) {
5912     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5913   }
5914
5915   /* If we need the chess program but it's dead, restart it */
5916   ResurrectChessProgram();
5917
5918   /* A user move restarts a paused game*/
5919   if (pausing)
5920     PauseEvent();
5921
5922   thinkOutput[0] = NULLCHAR;
5923
5924   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5925
5926   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5927
5928   if (gameMode == BeginningOfGame) {
5929     if (appData.noChessProgram) {
5930       gameMode = EditGame;
5931       SetGameInfo();
5932     } else {
5933       char buf[MSG_SIZ];
5934       gameMode = MachinePlaysBlack;
5935       StartClocks();
5936       SetGameInfo();
5937       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5938       DisplayTitle(buf);
5939       if (first.sendName) {
5940         sprintf(buf, "name %s\n", gameInfo.white);
5941         SendToProgram(buf, &first);
5942       }
5943       StartClocks();
5944     }
5945     ModeHighlight();
5946   }
5947
5948   /* Relay move to ICS or chess engine */
5949   if (appData.icsActive) {
5950     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5951         gameMode == IcsExamining) {
5952       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5953         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5954         SendToICS("draw ");
5955         SendMoveToICS(moveType, fromX, fromY, toX, toY);
5956       }
5957       // also send plain move, in case ICS does not understand atomic claims
5958       SendMoveToICS(moveType, fromX, fromY, toX, toY);
5959       ics_user_moved = 1;
5960     }
5961   } else {
5962     if (first.sendTime && (gameMode == BeginningOfGame ||
5963                            gameMode == MachinePlaysWhite ||
5964                            gameMode == MachinePlaysBlack)) {
5965       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5966     }
5967     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5968          // [HGM] book: if program might be playing, let it use book
5969         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5970         first.maybeThinking = TRUE;
5971     } else SendMoveToProgram(forwardMostMove-1, &first);
5972     if (currentMove == cmailOldMove + 1) {
5973       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5974     }
5975   }
5976
5977   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5978
5979   switch (gameMode) {
5980   case EditGame:
5981     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5982     case MT_NONE:
5983     case MT_CHECK:
5984       break;
5985     case MT_CHECKMATE:
5986     case MT_STAINMATE:
5987       if (WhiteOnMove(currentMove)) {
5988         GameEnds(BlackWins, "Black mates", GE_PLAYER);
5989       } else {
5990         GameEnds(WhiteWins, "White mates", GE_PLAYER);
5991       }
5992       break;
5993     case MT_STALEMATE:
5994       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5995       break;
5996     }
5997     break;
5998     
5999   case MachinePlaysBlack:
6000   case MachinePlaysWhite:
6001     /* disable certain menu options while machine is thinking */
6002     SetMachineThinkingEnables();
6003     break;
6004
6005   default:
6006     break;
6007   }
6008
6009   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6010         
6011   if(bookHit) { // [HGM] book: simulate book reply
6012         static char bookMove[MSG_SIZ]; // a bit generous?
6013
6014         programStats.nodes = programStats.depth = programStats.time = 
6015         programStats.score = programStats.got_only_move = 0;
6016         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6017
6018         strcpy(bookMove, "move ");
6019         strcat(bookMove, bookHit);
6020         HandleMachineMove(bookMove, &first);
6021   }
6022   return 1;
6023 }
6024
6025 void
6026 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6027      int fromX, fromY, toX, toY;
6028      int promoChar;
6029 {
6030     /* [HGM] This routine was added to allow calling of its two logical
6031        parts from other modules in the old way. Before, UserMoveEvent()
6032        automatically called FinishMove() if the move was OK, and returned
6033        otherwise. I separated the two, in order to make it possible to
6034        slip a promotion popup in between. But that it always needs two
6035        calls, to the first part, (now called UserMoveTest() ), and to
6036        FinishMove if the first part succeeded. Calls that do not need
6037        to do anything in between, can call this routine the old way. 
6038     */
6039     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6040 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6041     if(moveType == AmbiguousMove)
6042         DrawPosition(FALSE, boards[currentMove]);
6043     else if(moveType != ImpossibleMove && moveType != Comment)
6044         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6045 }
6046
6047 void
6048 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6049      Board board;
6050      int flags;
6051      ChessMove kind;
6052      int rf, ff, rt, ft;
6053      VOIDSTAR closure;
6054 {
6055     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6056     Markers *m = (Markers *) closure;
6057     if(rf == fromY && ff == fromX)
6058         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6059                          || kind == WhiteCapturesEnPassant
6060                          || kind == BlackCapturesEnPassant);
6061     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6062 }
6063
6064 void
6065 MarkTargetSquares(int clear)
6066 {
6067   int x, y;
6068   if(!appData.markers || !appData.highlightDragging || 
6069      !appData.testLegality || gameMode == EditPosition) return;
6070   if(clear) {
6071     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6072   } else {
6073     int capt = 0;
6074     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6075     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6076       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6077       if(capt)
6078       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6079     }
6080   }
6081   DrawPosition(TRUE, NULL);
6082 }
6083
6084 void LeftClick(ClickType clickType, int xPix, int yPix)
6085 {
6086     int x, y;
6087     Boolean saveAnimate;
6088     static int second = 0, promotionChoice = 0;
6089     char promoChoice = NULLCHAR;
6090
6091     if(appData.seekGraph && appData.icsActive && loggedOn &&
6092         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6093         SeekGraphClick(clickType, xPix, yPix, 0);
6094         return;
6095     }
6096
6097     if (clickType == Press) ErrorPopDown();
6098     MarkTargetSquares(1);
6099
6100     x = EventToSquare(xPix, BOARD_WIDTH);
6101     y = EventToSquare(yPix, BOARD_HEIGHT);
6102     if (!flipView && y >= 0) {
6103         y = BOARD_HEIGHT - 1 - y;
6104     }
6105     if (flipView && x >= 0) {
6106         x = BOARD_WIDTH - 1 - x;
6107     }
6108
6109     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6110         if(clickType == Release) return; // ignore upclick of click-click destination
6111         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6112         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6113         if(gameInfo.holdingsWidth && 
6114                 (WhiteOnMove(currentMove) 
6115                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6116                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6117             // click in right holdings, for determining promotion piece
6118             ChessSquare p = boards[currentMove][y][x];
6119             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6120             if(p != EmptySquare) {
6121                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6122                 fromX = fromY = -1;
6123                 return;
6124             }
6125         }
6126         DrawPosition(FALSE, boards[currentMove]);
6127         return;
6128     }
6129
6130     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6131     if(clickType == Press
6132             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6133               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6134               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6135         return;
6136
6137     autoQueen = appData.alwaysPromoteToQueen;
6138
6139     if (fromX == -1) {
6140       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6141         if (clickType == Press) {
6142             /* First square */
6143             if (OKToStartUserMove(x, y)) {
6144                 fromX = x;
6145                 fromY = y;
6146                 second = 0;
6147                 MarkTargetSquares(0);
6148                 DragPieceBegin(xPix, yPix);
6149                 if (appData.highlightDragging) {
6150                     SetHighlights(x, y, -1, -1);
6151                 }
6152             }
6153         }
6154         return;
6155       }
6156     }
6157
6158     /* fromX != -1 */
6159     if (clickType == Press && gameMode != EditPosition) {
6160         ChessSquare fromP;
6161         ChessSquare toP;
6162         int frc;
6163
6164         // ignore off-board to clicks
6165         if(y < 0 || x < 0) return;
6166
6167         /* Check if clicking again on the same color piece */
6168         fromP = boards[currentMove][fromY][fromX];
6169         toP = boards[currentMove][y][x];
6170         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6171         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6172              WhitePawn <= toP && toP <= WhiteKing &&
6173              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6174              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6175             (BlackPawn <= fromP && fromP <= BlackKing && 
6176              BlackPawn <= toP && toP <= BlackKing &&
6177              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6178              !(fromP == BlackKing && toP == BlackRook && frc))) {
6179             /* Clicked again on same color piece -- changed his mind */
6180             second = (x == fromX && y == fromY);
6181            if(!second || !OnlyMove(&x, &y, TRUE)) {
6182             if (appData.highlightDragging) {
6183                 SetHighlights(x, y, -1, -1);
6184             } else {
6185                 ClearHighlights();
6186             }
6187             if (OKToStartUserMove(x, y)) {
6188                 fromX = x;
6189                 fromY = y;
6190                 MarkTargetSquares(0);
6191                 DragPieceBegin(xPix, yPix);
6192             }
6193             return;
6194            }
6195         }
6196         // ignore clicks on holdings
6197         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6198     }
6199
6200     if (clickType == Release && x == fromX && y == fromY) {
6201         DragPieceEnd(xPix, yPix);
6202         if (appData.animateDragging) {
6203             /* Undo animation damage if any */
6204             DrawPosition(FALSE, NULL);
6205         }
6206         if (second) {
6207             /* Second up/down in same square; just abort move */
6208             second = 0;
6209             fromX = fromY = -1;
6210             ClearHighlights();
6211             gotPremove = 0;
6212             ClearPremoveHighlights();
6213         } else {
6214             /* First upclick in same square; start click-click mode */
6215             SetHighlights(x, y, -1, -1);
6216         }
6217         return;
6218     }
6219
6220     /* we now have a different from- and (possibly off-board) to-square */
6221     /* Completed move */
6222     toX = x;
6223     toY = y;
6224     saveAnimate = appData.animate;
6225     if (clickType == Press) {
6226         /* Finish clickclick move */
6227         if (appData.animate || appData.highlightLastMove) {
6228             SetHighlights(fromX, fromY, toX, toY);
6229         } else {
6230             ClearHighlights();
6231         }
6232     } else {
6233         /* Finish drag move */
6234         if (appData.highlightLastMove) {
6235             SetHighlights(fromX, fromY, toX, toY);
6236         } else {
6237             ClearHighlights();
6238         }
6239         DragPieceEnd(xPix, yPix);
6240         /* Don't animate move and drag both */
6241         appData.animate = FALSE;
6242     }
6243
6244     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6245     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6246         ChessSquare piece = boards[currentMove][fromY][fromX];
6247         if(gameMode == EditPosition && piece != EmptySquare &&
6248            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6249             int n;
6250              
6251             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6252                 n = PieceToNumber(piece - (int)BlackPawn);
6253                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6254                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6255                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6256             } else
6257             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6258                 n = PieceToNumber(piece);
6259                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6260                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6261                 boards[currentMove][n][BOARD_WIDTH-2]++;
6262             }
6263             boards[currentMove][fromY][fromX] = EmptySquare;
6264         }
6265         ClearHighlights();
6266         fromX = fromY = -1;
6267         DrawPosition(TRUE, boards[currentMove]);
6268         return;
6269     }
6270
6271     // off-board moves should not be highlighted
6272     if(x < 0 || x < 0) ClearHighlights();
6273
6274     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6275         SetHighlights(fromX, fromY, toX, toY);
6276         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6277             // [HGM] super: promotion to captured piece selected from holdings
6278             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6279             promotionChoice = TRUE;
6280             // kludge follows to temporarily execute move on display, without promoting yet
6281             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6282             boards[currentMove][toY][toX] = p;
6283             DrawPosition(FALSE, boards[currentMove]);
6284             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6285             boards[currentMove][toY][toX] = q;
6286             DisplayMessage("Click in holdings to choose piece", "");
6287             return;
6288         }
6289         PromotionPopUp();
6290     } else {
6291         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6292         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6293         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6294         fromX = fromY = -1;
6295     }
6296     appData.animate = saveAnimate;
6297     if (appData.animate || appData.animateDragging) {
6298         /* Undo animation damage if needed */
6299         DrawPosition(FALSE, NULL);
6300     }
6301 }
6302
6303 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6304 {   // front-end-free part taken out of PieceMenuPopup
6305     int whichMenu; int xSqr, ySqr;
6306
6307     if(seekGraphUp) { // [HGM] seekgraph
6308         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6309         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6310         return -2;
6311     }
6312
6313     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6314          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6315         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6316         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6317         return -2;
6318     }
6319
6320     xSqr = EventToSquare(x, BOARD_WIDTH);
6321     ySqr = EventToSquare(y, BOARD_HEIGHT);
6322     if (action == Release) UnLoadPV(); // [HGM] pv
6323     if (action != Press) return -2; // return code to be ignored
6324     switch (gameMode) {
6325       case IcsExamining:
6326         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6327       case EditPosition:
6328         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6329         if (xSqr < 0 || ySqr < 0) return -1;\r
6330         whichMenu = 0; // edit-position menu
6331         break;
6332       case IcsObserving:
6333         if(!appData.icsEngineAnalyze) return -1;
6334       case IcsPlayingWhite:
6335       case IcsPlayingBlack:
6336         if(!appData.zippyPlay) goto noZip;
6337       case AnalyzeMode:
6338       case AnalyzeFile:
6339       case MachinePlaysWhite:
6340       case MachinePlaysBlack:
6341       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6342         if (!appData.dropMenu) {
6343           LoadPV(x, y);
6344           return 2; // flag front-end to grab mouse events
6345         }
6346         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6347            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6348       case EditGame:
6349       noZip:
6350         if (xSqr < 0 || ySqr < 0) return -1;
6351         if (!appData.dropMenu || appData.testLegality &&
6352             gameInfo.variant != VariantBughouse &&
6353             gameInfo.variant != VariantCrazyhouse) return -1;
6354         whichMenu = 1; // drop menu
6355         break;
6356       default:
6357         return -1;
6358     }
6359
6360     if (((*fromX = xSqr) < 0) ||
6361         ((*fromY = ySqr) < 0)) {
6362         *fromX = *fromY = -1;
6363         return -1;
6364     }
6365     if (flipView)
6366       *fromX = BOARD_WIDTH - 1 - *fromX;
6367     else
6368       *fromY = BOARD_HEIGHT - 1 - *fromY;
6369
6370     return whichMenu;
6371 }
6372
6373 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6374 {
6375 //    char * hint = lastHint;
6376     FrontEndProgramStats stats;
6377
6378     stats.which = cps == &first ? 0 : 1;
6379     stats.depth = cpstats->depth;
6380     stats.nodes = cpstats->nodes;
6381     stats.score = cpstats->score;
6382     stats.time = cpstats->time;
6383     stats.pv = cpstats->movelist;
6384     stats.hint = lastHint;
6385     stats.an_move_index = 0;
6386     stats.an_move_count = 0;
6387
6388     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6389         stats.hint = cpstats->move_name;
6390         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6391         stats.an_move_count = cpstats->nr_moves;
6392     }
6393
6394     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6395
6396     SetProgramStats( &stats );
6397 }
6398
6399 int
6400 Adjudicate(ChessProgramState *cps)
6401 {       // [HGM] some adjudications useful with buggy engines
6402         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6403         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6404         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6405         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6406         int k, count = 0; static int bare = 1;
6407         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6408         Boolean canAdjudicate = !appData.icsActive;
6409
6410         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6411         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6412             if( appData.testLegality )
6413             {   /* [HGM] Some more adjudications for obstinate engines */
6414                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6415                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6416                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6417                 static int moveCount = 6;
6418                 ChessMove result;
6419                 char *reason = NULL;
6420
6421                 /* Count what is on board. */
6422                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6423                 {   ChessSquare p = boards[forwardMostMove][i][j];
6424                     int m=i;
6425
6426                     switch((int) p)
6427                     {   /* count B,N,R and other of each side */
6428                         case WhiteKing:
6429                         case BlackKing:
6430                              NrK++; break; // [HGM] atomic: count Kings
6431                         case WhiteKnight:
6432                              NrWN++; break;
6433                         case WhiteBishop:
6434                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6435                              bishopsColor |= 1 << ((i^j)&1);
6436                              NrWB++; break;
6437                         case BlackKnight:
6438                              NrBN++; break;
6439                         case BlackBishop:
6440                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6441                              bishopsColor |= 1 << ((i^j)&1);
6442                              NrBB++; break;
6443                         case WhiteRook:
6444                              NrWR++; break;
6445                         case BlackRook:
6446                              NrBR++; break;
6447                         case WhiteQueen:
6448                              NrWQ++; break;
6449                         case BlackQueen:
6450                              NrBQ++; break;
6451                         case EmptySquare: 
6452                              break;
6453                         case BlackPawn:
6454                              m = 7-i;
6455                         case WhitePawn:
6456                              PawnAdvance += m; NrPawns++;
6457                     }
6458                     NrPieces += (p != EmptySquare);
6459                     NrW += ((int)p < (int)BlackPawn);
6460                     if(gameInfo.variant == VariantXiangqi && 
6461                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6462                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6463                         NrW -= ((int)p < (int)BlackPawn);
6464                     }
6465                 }
6466
6467                 /* Some material-based adjudications that have to be made before stalemate test */
6468                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6469                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6470                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6471                      if(canAdjudicate && appData.checkMates) {
6472                          if(engineOpponent)
6473                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6474                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6475                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6476                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6477                          return 1;
6478                      }
6479                 }
6480
6481                 /* Bare King in Shatranj (loses) or Losers (wins) */
6482                 if( NrW == 1 || NrPieces - NrW == 1) {
6483                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6484                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6485                      if(canAdjudicate && appData.checkMates) {
6486                          if(engineOpponent)
6487                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6488                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6489                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6490                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6491                          return 1;
6492                      }
6493                   } else
6494                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6495                   {    /* bare King */
6496                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6497                         if(canAdjudicate && appData.checkMates) {
6498                             /* but only adjudicate if adjudication enabled */
6499                             if(engineOpponent)
6500                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6501                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6502                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6503                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6504                             return 1;
6505                         }
6506                   }
6507                 } else bare = 1;
6508
6509
6510             // don't wait for engine to announce game end if we can judge ourselves
6511             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6512               case MT_CHECK:
6513                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6514                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6515                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6516                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6517                             checkCnt++;
6518                         if(checkCnt >= 2) {
6519                             reason = "Xboard adjudication: 3rd check";
6520                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6521                             break;
6522                         }
6523                     }
6524                 }
6525               case MT_NONE:
6526               default:
6527                 break;
6528               case MT_STALEMATE:
6529               case MT_STAINMATE:
6530                 reason = "Xboard adjudication: Stalemate";
6531                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6532                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6533                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6534                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6535                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6536                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6537                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6538                                                                         EP_CHECKMATE : EP_WINS);
6539                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6540                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6541                 }
6542                 break;
6543               case MT_CHECKMATE:
6544                 reason = "Xboard adjudication: Checkmate";
6545                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6546                 break;
6547             }
6548
6549                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6550                     case EP_STALEMATE:
6551                         result = GameIsDrawn; break;
6552                     case EP_CHECKMATE:
6553                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6554                     case EP_WINS:
6555                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6556                     default:
6557                         result = (ChessMove) 0;
6558                 }
6559                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6560                     if(engineOpponent)
6561                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6562                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563                     GameEnds( result, reason, GE_XBOARD );
6564                     return 1;
6565                 }
6566
6567                 /* Next absolutely insufficient mating material. */
6568                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6569                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6570                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6571                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6572                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6573
6574                      /* always flag draws, for judging claims */
6575                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6576
6577                      if(canAdjudicate && appData.materialDraws) {
6578                          /* but only adjudicate them if adjudication enabled */
6579                          if(engineOpponent) {
6580                            SendToProgram("force\n", engineOpponent); // suppress reply
6581                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6582                          }
6583                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6584                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6585                          return 1;
6586                      }
6587                 }
6588
6589                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6590                 if(NrPieces == 4 && 
6591                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6592                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6593                    || NrWN==2 || NrBN==2     /* KNNK */
6594                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6595                   ) ) {
6596                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6597                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6598                           if(engineOpponent) {
6599                             SendToProgram("force\n", engineOpponent); // suppress reply
6600                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6601                           }
6602                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6604                           return 1;
6605                      }
6606                 } else moveCount = 6;
6607             }
6608         }
6609           
6610         if (appData.debugMode) { int i;
6611             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6612                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6613                     appData.drawRepeats);
6614             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6615               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6616             
6617         }
6618
6619         // Repetition draws and 50-move rule can be applied independently of legality testing
6620
6621                 /* Check for rep-draws */
6622                 count = 0;
6623                 for(k = forwardMostMove-2;
6624                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6625                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6626                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6627                     k-=2)
6628                 {   int rights=0;
6629                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6630                         /* compare castling rights */
6631                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6632                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6633                                 rights++; /* King lost rights, while rook still had them */
6634                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6635                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6636                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6637                                    rights++; /* but at least one rook lost them */
6638                         }
6639                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6640                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6641                                 rights++; 
6642                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6643                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6644                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6645                                    rights++;
6646                         }
6647                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6648                             && appData.drawRepeats > 1) {
6649                              /* adjudicate after user-specified nr of repeats */
6650                              if(engineOpponent) {
6651                                SendToProgram("force\n", engineOpponent); // suppress reply
6652                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6653                              }
6654                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6655                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6656                                 // [HGM] xiangqi: check for forbidden perpetuals
6657                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6658                                 for(m=forwardMostMove; m>k; m-=2) {
6659                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6660                                         ourPerpetual = 0; // the current mover did not always check
6661                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6662                                         hisPerpetual = 0; // the opponent did not always check
6663                                 }
6664                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6665                                                                         ourPerpetual, hisPerpetual);
6666                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6667                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6668                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6669                                     return 1;
6670                                 }
6671                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6672                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6673                                 // Now check for perpetual chases
6674                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6675                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6676                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6677                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6678                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6679                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6680                                         return 1;
6681                                     }
6682                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6683                                         break; // Abort repetition-checking loop.
6684                                 }
6685                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6686                              }
6687                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6688                              return 1;
6689                         }
6690                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6691                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6692                     }
6693                 }
6694
6695                 /* Now we test for 50-move draws. Determine ply count */
6696                 count = forwardMostMove;
6697                 /* look for last irreversble move */
6698                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6699                     count--;
6700                 /* if we hit starting position, add initial plies */
6701                 if( count == backwardMostMove )
6702                     count -= initialRulePlies;
6703                 count = forwardMostMove - count; 
6704                 if( count >= 100)
6705                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6706                          /* this is used to judge if draw claims are legal */
6707                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6708                          if(engineOpponent) {
6709                            SendToProgram("force\n", engineOpponent); // suppress reply
6710                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6711                          }
6712                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6713                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6714                          return 1;
6715                 }
6716
6717                 /* if draw offer is pending, treat it as a draw claim
6718                  * when draw condition present, to allow engines a way to
6719                  * claim draws before making their move to avoid a race
6720                  * condition occurring after their move
6721                  */
6722                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6723                          char *p = NULL;
6724                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6725                              p = "Draw claim: 50-move rule";
6726                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6727                              p = "Draw claim: 3-fold repetition";
6728                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6729                              p = "Draw claim: insufficient mating material";
6730                          if( p != NULL && canAdjudicate) {
6731                              if(engineOpponent) {
6732                                SendToProgram("force\n", engineOpponent); // suppress reply
6733                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6734                              }
6735                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6736                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6737                              return 1;
6738                          }
6739                 }
6740
6741                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6742                     if(engineOpponent) {
6743                       SendToProgram("force\n", engineOpponent); // suppress reply
6744                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6745                     }
6746                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6747                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6748                     return 1;
6749                 }
6750         return 0;
6751 }
6752
6753 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6754 {   // [HGM] book: this routine intercepts moves to simulate book replies
6755     char *bookHit = NULL;
6756
6757     //first determine if the incoming move brings opponent into his book
6758     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6759         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6760     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6761     if(bookHit != NULL && !cps->bookSuspend) {
6762         // make sure opponent is not going to reply after receiving move to book position
6763         SendToProgram("force\n", cps);
6764         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6765     }
6766     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6767     // now arrange restart after book miss
6768     if(bookHit) {
6769         // after a book hit we never send 'go', and the code after the call to this routine
6770         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6771         char buf[MSG_SIZ];
6772         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6773         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6774         SendToProgram(buf, cps);
6775         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6776     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6777         SendToProgram("go\n", cps);
6778         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6779     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6780         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6781             SendToProgram("go\n", cps); 
6782         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6783     }
6784     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6785 }
6786
6787 char *savedMessage;
6788 ChessProgramState *savedState;
6789 void DeferredBookMove(void)
6790 {
6791         if(savedState->lastPing != savedState->lastPong)
6792                     ScheduleDelayedEvent(DeferredBookMove, 10);
6793         else
6794         HandleMachineMove(savedMessage, savedState);
6795 }
6796
6797 void
6798 HandleMachineMove(message, cps)
6799      char *message;
6800      ChessProgramState *cps;
6801 {
6802     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6803     char realname[MSG_SIZ];
6804     int fromX, fromY, toX, toY;
6805     ChessMove moveType;
6806     char promoChar;
6807     char *p;
6808     int machineWhite;
6809     char *bookHit;
6810
6811     cps->userError = 0;
6812
6813 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6814     /*
6815      * Kludge to ignore BEL characters
6816      */
6817     while (*message == '\007') message++;
6818
6819     /*
6820      * [HGM] engine debug message: ignore lines starting with '#' character
6821      */
6822     if(cps->debug && *message == '#') return;
6823
6824     /*
6825      * Look for book output
6826      */
6827     if (cps == &first && bookRequested) {
6828         if (message[0] == '\t' || message[0] == ' ') {
6829             /* Part of the book output is here; append it */
6830             strcat(bookOutput, message);
6831             strcat(bookOutput, "  \n");
6832             return;
6833         } else if (bookOutput[0] != NULLCHAR) {
6834             /* All of book output has arrived; display it */
6835             char *p = bookOutput;
6836             while (*p != NULLCHAR) {
6837                 if (*p == '\t') *p = ' ';
6838                 p++;
6839             }
6840             DisplayInformation(bookOutput);
6841             bookRequested = FALSE;
6842             /* Fall through to parse the current output */
6843         }
6844     }
6845
6846     /*
6847      * Look for machine move.
6848      */
6849     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6850         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6851     {
6852         /* This method is only useful on engines that support ping */
6853         if (cps->lastPing != cps->lastPong) {
6854           if (gameMode == BeginningOfGame) {
6855             /* Extra move from before last new; ignore */
6856             if (appData.debugMode) {
6857                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6858             }
6859           } else {
6860             if (appData.debugMode) {
6861                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6862                         cps->which, gameMode);
6863             }
6864
6865             SendToProgram("undo\n", cps);
6866           }
6867           return;
6868         }
6869
6870         switch (gameMode) {
6871           case BeginningOfGame:
6872             /* Extra move from before last reset; ignore */
6873             if (appData.debugMode) {
6874                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6875             }
6876             return;
6877
6878           case EndOfGame:
6879           case IcsIdle:
6880           default:
6881             /* Extra move after we tried to stop.  The mode test is
6882                not a reliable way of detecting this problem, but it's
6883                the best we can do on engines that don't support ping.
6884             */
6885             if (appData.debugMode) {
6886                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6887                         cps->which, gameMode);
6888             }
6889             SendToProgram("undo\n", cps);
6890             return;
6891
6892           case MachinePlaysWhite:
6893           case IcsPlayingWhite:
6894             machineWhite = TRUE;
6895             break;
6896
6897           case MachinePlaysBlack:
6898           case IcsPlayingBlack:
6899             machineWhite = FALSE;
6900             break;
6901
6902           case TwoMachinesPlay:
6903             machineWhite = (cps->twoMachinesColor[0] == 'w');
6904             break;
6905         }
6906         if (WhiteOnMove(forwardMostMove) != machineWhite) {
6907             if (appData.debugMode) {
6908                 fprintf(debugFP,
6909                         "Ignoring move out of turn by %s, gameMode %d"
6910                         ", forwardMost %d\n",
6911                         cps->which, gameMode, forwardMostMove);
6912             }
6913             return;
6914         }
6915
6916     if (appData.debugMode) { int f = forwardMostMove;
6917         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6918                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6919                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6920     }
6921         if(cps->alphaRank) AlphaRank(machineMove, 4);
6922         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6923                               &fromX, &fromY, &toX, &toY, &promoChar)) {
6924             /* Machine move could not be parsed; ignore it. */
6925             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6926                     machineMove, cps->which);
6927             DisplayError(buf1, 0);
6928             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6929                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6930             if (gameMode == TwoMachinesPlay) {
6931               GameEnds(machineWhite ? BlackWins : WhiteWins,
6932                        buf1, GE_XBOARD);
6933             }
6934             return;
6935         }
6936
6937         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6938         /* So we have to redo legality test with true e.p. status here,  */
6939         /* to make sure an illegal e.p. capture does not slip through,   */
6940         /* to cause a forfeit on a justified illegal-move complaint      */
6941         /* of the opponent.                                              */
6942         if( gameMode==TwoMachinesPlay && appData.testLegality
6943             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6944                                                               ) {
6945            ChessMove moveType;
6946            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6947                              fromY, fromX, toY, toX, promoChar);
6948             if (appData.debugMode) {
6949                 int i;
6950                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6951                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6952                 fprintf(debugFP, "castling rights\n");
6953             }
6954             if(moveType == IllegalMove) {
6955                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6956                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6957                 GameEnds(machineWhite ? BlackWins : WhiteWins,
6958                            buf1, GE_XBOARD);
6959                 return;
6960            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6961            /* [HGM] Kludge to handle engines that send FRC-style castling
6962               when they shouldn't (like TSCP-Gothic) */
6963            switch(moveType) {
6964              case WhiteASideCastleFR:
6965              case BlackASideCastleFR:
6966                toX+=2;
6967                currentMoveString[2]++;
6968                break;
6969              case WhiteHSideCastleFR:
6970              case BlackHSideCastleFR:
6971                toX--;
6972                currentMoveString[2]--;
6973                break;
6974              default: ; // nothing to do, but suppresses warning of pedantic compilers
6975            }
6976         }
6977         hintRequested = FALSE;
6978         lastHint[0] = NULLCHAR;
6979         bookRequested = FALSE;
6980         /* Program may be pondering now */
6981         cps->maybeThinking = TRUE;
6982         if (cps->sendTime == 2) cps->sendTime = 1;
6983         if (cps->offeredDraw) cps->offeredDraw--;
6984
6985         /* currentMoveString is set as a side-effect of ParseOneMove */
6986         strcpy(machineMove, currentMoveString);
6987         strcat(machineMove, "\n");
6988         strcpy(moveList[forwardMostMove], machineMove);
6989
6990         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6991
6992         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6993         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6994             int count = 0;
6995
6996             while( count < adjudicateLossPlies ) {
6997                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6998
6999                 if( count & 1 ) {
7000                     score = -score; /* Flip score for winning side */
7001                 }
7002
7003                 if( score > adjudicateLossThreshold ) {
7004                     break;
7005                 }
7006
7007                 count++;
7008             }
7009
7010             if( count >= adjudicateLossPlies ) {
7011                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7012
7013                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7014                     "Xboard adjudication", 
7015                     GE_XBOARD );
7016
7017                 return;
7018             }
7019         }
7020
7021         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7022
7023 #if ZIPPY
7024         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7025             first.initDone) {
7026           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7027                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7028                 SendToICS("draw ");
7029                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7030           }
7031           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7032           ics_user_moved = 1;
7033           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7034                 char buf[3*MSG_SIZ];
7035
7036                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7037                         programStats.score / 100.,
7038                         programStats.depth,
7039                         programStats.time / 100.,
7040                         (unsigned int)programStats.nodes,
7041                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7042                         programStats.movelist);
7043                 SendToICS(buf);
7044 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7045           }
7046         }
7047 #endif
7048
7049         /* [AS] Save move info and clear stats for next move */
7050         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7051         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7052         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7053         ClearProgramStats();
7054         thinkOutput[0] = NULLCHAR;
7055         hiddenThinkOutputState = 0;
7056
7057         bookHit = NULL;
7058         if (gameMode == TwoMachinesPlay) {
7059             /* [HGM] relaying draw offers moved to after reception of move */
7060             /* and interpreting offer as claim if it brings draw condition */
7061             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7062                 SendToProgram("draw\n", cps->other);
7063             }
7064             if (cps->other->sendTime) {
7065                 SendTimeRemaining(cps->other,
7066                                   cps->other->twoMachinesColor[0] == 'w');
7067             }
7068             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7069             if (firstMove && !bookHit) {
7070                 firstMove = FALSE;
7071                 if (cps->other->useColors) {
7072                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7073                 }
7074                 SendToProgram("go\n", cps->other);
7075             }
7076             cps->other->maybeThinking = TRUE;
7077         }
7078
7079         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7080         
7081         if (!pausing && appData.ringBellAfterMoves) {
7082             RingBell();
7083         }
7084
7085         /* 
7086          * Reenable menu items that were disabled while
7087          * machine was thinking
7088          */
7089         if (gameMode != TwoMachinesPlay)
7090             SetUserThinkingEnables();
7091
7092         // [HGM] book: after book hit opponent has received move and is now in force mode
7093         // force the book reply into it, and then fake that it outputted this move by jumping
7094         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7095         if(bookHit) {
7096                 static char bookMove[MSG_SIZ]; // a bit generous?
7097
7098                 strcpy(bookMove, "move ");
7099                 strcat(bookMove, bookHit);
7100                 message = bookMove;
7101                 cps = cps->other;
7102                 programStats.nodes = programStats.depth = programStats.time = 
7103                 programStats.score = programStats.got_only_move = 0;
7104                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7105
7106                 if(cps->lastPing != cps->lastPong) {
7107                     savedMessage = message; // args for deferred call
7108                     savedState = cps;
7109                     ScheduleDelayedEvent(DeferredBookMove, 10);
7110                     return;
7111                 }
7112                 goto FakeBookMove;
7113         }
7114
7115         return;
7116     }
7117
7118     /* Set special modes for chess engines.  Later something general
7119      *  could be added here; for now there is just one kludge feature,
7120      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7121      *  when "xboard" is given as an interactive command.
7122      */
7123     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7124         cps->useSigint = FALSE;
7125         cps->useSigterm = FALSE;
7126     }
7127     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7128       ParseFeatures(message+8, cps);
7129       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7130     }
7131
7132     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7133      * want this, I was asked to put it in, and obliged.
7134      */
7135     if (!strncmp(message, "setboard ", 9)) {
7136         Board initial_position;
7137
7138         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7139
7140         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7141             DisplayError(_("Bad FEN received from engine"), 0);
7142             return ;
7143         } else {
7144            Reset(TRUE, FALSE);
7145            CopyBoard(boards[0], initial_position);
7146            initialRulePlies = FENrulePlies;
7147            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7148            else gameMode = MachinePlaysBlack;                 
7149            DrawPosition(FALSE, boards[currentMove]);
7150         }
7151         return;
7152     }
7153
7154     /*
7155      * Look for communication commands
7156      */
7157     if (!strncmp(message, "telluser ", 9)) {
7158         DisplayNote(message + 9);
7159         return;
7160     }
7161     if (!strncmp(message, "tellusererror ", 14)) {
7162         cps->userError = 1;
7163         DisplayError(message + 14, 0);
7164         return;
7165     }
7166     if (!strncmp(message, "tellopponent ", 13)) {
7167       if (appData.icsActive) {
7168         if (loggedOn) {
7169           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7170           SendToICS(buf1);
7171         }
7172       } else {
7173         DisplayNote(message + 13);
7174       }
7175       return;
7176     }
7177     if (!strncmp(message, "tellothers ", 11)) {
7178       if (appData.icsActive) {
7179         if (loggedOn) {
7180           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7181           SendToICS(buf1);
7182         }
7183       }
7184       return;
7185     }
7186     if (!strncmp(message, "tellall ", 8)) {
7187       if (appData.icsActive) {
7188         if (loggedOn) {
7189           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7190           SendToICS(buf1);
7191         }
7192       } else {
7193         DisplayNote(message + 8);
7194       }
7195       return;
7196     }
7197     if (strncmp(message, "warning", 7) == 0) {
7198         /* Undocumented feature, use tellusererror in new code */
7199         DisplayError(message, 0);
7200         return;
7201     }
7202     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7203         strcpy(realname, cps->tidy);
7204         strcat(realname, " query");
7205         AskQuestion(realname, buf2, buf1, cps->pr);
7206         return;
7207     }
7208     /* Commands from the engine directly to ICS.  We don't allow these to be 
7209      *  sent until we are logged on. Crafty kibitzes have been known to 
7210      *  interfere with the login process.
7211      */
7212     if (loggedOn) {
7213         if (!strncmp(message, "tellics ", 8)) {
7214             SendToICS(message + 8);
7215             SendToICS("\n");
7216             return;
7217         }
7218         if (!strncmp(message, "tellicsnoalias ", 15)) {
7219             SendToICS(ics_prefix);
7220             SendToICS(message + 15);
7221             SendToICS("\n");
7222             return;
7223         }
7224         /* The following are for backward compatibility only */
7225         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7226             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7227             SendToICS(ics_prefix);
7228             SendToICS(message);
7229             SendToICS("\n");
7230             return;
7231         }
7232     }
7233     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7234         return;
7235     }
7236     /*
7237      * If the move is illegal, cancel it and redraw the board.
7238      * Also deal with other error cases.  Matching is rather loose
7239      * here to accommodate engines written before the spec.
7240      */
7241     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7242         strncmp(message, "Error", 5) == 0) {
7243         if (StrStr(message, "name") || 
7244             StrStr(message, "rating") || StrStr(message, "?") ||
7245             StrStr(message, "result") || StrStr(message, "board") ||
7246             StrStr(message, "bk") || StrStr(message, "computer") ||
7247             StrStr(message, "variant") || StrStr(message, "hint") ||
7248             StrStr(message, "random") || StrStr(message, "depth") ||
7249             StrStr(message, "accepted")) {
7250             return;
7251         }
7252         if (StrStr(message, "protover")) {
7253           /* Program is responding to input, so it's apparently done
7254              initializing, and this error message indicates it is
7255              protocol version 1.  So we don't need to wait any longer
7256              for it to initialize and send feature commands. */
7257           FeatureDone(cps, 1);
7258           cps->protocolVersion = 1;
7259           return;
7260         }
7261         cps->maybeThinking = FALSE;
7262
7263         if (StrStr(message, "draw")) {
7264             /* Program doesn't have "draw" command */
7265             cps->sendDrawOffers = 0;
7266             return;
7267         }
7268         if (cps->sendTime != 1 &&
7269             (StrStr(message, "time") || StrStr(message, "otim"))) {
7270           /* Program apparently doesn't have "time" or "otim" command */
7271           cps->sendTime = 0;
7272           return;
7273         }
7274         if (StrStr(message, "analyze")) {
7275             cps->analysisSupport = FALSE;
7276             cps->analyzing = FALSE;
7277             Reset(FALSE, TRUE);
7278             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7279             DisplayError(buf2, 0);
7280             return;
7281         }
7282         if (StrStr(message, "(no matching move)st")) {
7283           /* Special kludge for GNU Chess 4 only */
7284           cps->stKludge = TRUE;
7285           SendTimeControl(cps, movesPerSession, timeControl,
7286                           timeIncrement, appData.searchDepth,
7287                           searchTime);
7288           return;
7289         }
7290         if (StrStr(message, "(no matching move)sd")) {
7291           /* Special kludge for GNU Chess 4 only */
7292           cps->sdKludge = TRUE;
7293           SendTimeControl(cps, movesPerSession, timeControl,
7294                           timeIncrement, appData.searchDepth,
7295                           searchTime);
7296           return;
7297         }
7298         if (!StrStr(message, "llegal")) {
7299             return;
7300         }
7301         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7302             gameMode == IcsIdle) return;
7303         if (forwardMostMove <= backwardMostMove) return;
7304         if (pausing) PauseEvent();
7305       if(appData.forceIllegal) {
7306             // [HGM] illegal: machine refused move; force position after move into it
7307           SendToProgram("force\n", cps);
7308           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7309                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7310                 // when black is to move, while there might be nothing on a2 or black
7311                 // might already have the move. So send the board as if white has the move.
7312                 // But first we must change the stm of the engine, as it refused the last move
7313                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7314                 if(WhiteOnMove(forwardMostMove)) {
7315                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7316                     SendBoard(cps, forwardMostMove); // kludgeless board
7317                 } else {
7318                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7319                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7320                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7321                 }
7322           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7323             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7324                  gameMode == TwoMachinesPlay)
7325               SendToProgram("go\n", cps);
7326             return;
7327       } else
7328         if (gameMode == PlayFromGameFile) {
7329             /* Stop reading this game file */
7330             gameMode = EditGame;
7331             ModeHighlight();
7332         }
7333         currentMove = forwardMostMove-1;
7334         DisplayMove(currentMove-1); /* before DisplayMoveError */
7335         SwitchClocks(forwardMostMove-1); // [HGM] race
7336         DisplayBothClocks();
7337         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7338                 parseList[currentMove], cps->which);
7339         DisplayMoveError(buf1);
7340         DrawPosition(FALSE, boards[currentMove]);
7341
7342         /* [HGM] illegal-move claim should forfeit game when Xboard */
7343         /* only passes fully legal moves                            */
7344         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7345             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7346                                 "False illegal-move claim", GE_XBOARD );
7347         }
7348         return;
7349     }
7350     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7351         /* Program has a broken "time" command that
7352            outputs a string not ending in newline.
7353            Don't use it. */
7354         cps->sendTime = 0;
7355     }
7356     
7357     /*
7358      * If chess program startup fails, exit with an error message.
7359      * Attempts to recover here are futile.
7360      */
7361     if ((StrStr(message, "unknown host") != NULL)
7362         || (StrStr(message, "No remote directory") != NULL)
7363         || (StrStr(message, "not found") != NULL)
7364         || (StrStr(message, "No such file") != NULL)
7365         || (StrStr(message, "can't alloc") != NULL)
7366         || (StrStr(message, "Permission denied") != NULL)) {
7367
7368         cps->maybeThinking = FALSE;
7369         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7370                 cps->which, cps->program, cps->host, message);
7371         RemoveInputSource(cps->isr);
7372         DisplayFatalError(buf1, 0, 1);
7373         return;
7374     }
7375     
7376     /* 
7377      * Look for hint output
7378      */
7379     if (sscanf(message, "Hint: %s", buf1) == 1) {
7380         if (cps == &first && hintRequested) {
7381             hintRequested = FALSE;
7382             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7383                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7384                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7385                                     PosFlags(forwardMostMove),
7386                                     fromY, fromX, toY, toX, promoChar, buf1);
7387                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7388                 DisplayInformation(buf2);
7389             } else {
7390                 /* Hint move could not be parsed!? */
7391               snprintf(buf2, sizeof(buf2),
7392                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7393                         buf1, cps->which);
7394                 DisplayError(buf2, 0);
7395             }
7396         } else {
7397             strcpy(lastHint, buf1);
7398         }
7399         return;
7400     }
7401
7402     /*
7403      * Ignore other messages if game is not in progress
7404      */
7405     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7406         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7407
7408     /*
7409      * look for win, lose, draw, or draw offer
7410      */
7411     if (strncmp(message, "1-0", 3) == 0) {
7412         char *p, *q, *r = "";
7413         p = strchr(message, '{');
7414         if (p) {
7415             q = strchr(p, '}');
7416             if (q) {
7417                 *q = NULLCHAR;
7418                 r = p + 1;
7419             }
7420         }
7421         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7422         return;
7423     } else if (strncmp(message, "0-1", 3) == 0) {
7424         char *p, *q, *r = "";
7425         p = strchr(message, '{');
7426         if (p) {
7427             q = strchr(p, '}');
7428             if (q) {
7429                 *q = NULLCHAR;
7430                 r = p + 1;
7431             }
7432         }
7433         /* Kludge for Arasan 4.1 bug */
7434         if (strcmp(r, "Black resigns") == 0) {
7435             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7436             return;
7437         }
7438         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7439         return;
7440     } else if (strncmp(message, "1/2", 3) == 0) {
7441         char *p, *q, *r = "";
7442         p = strchr(message, '{');
7443         if (p) {
7444             q = strchr(p, '}');
7445             if (q) {
7446                 *q = NULLCHAR;
7447                 r = p + 1;
7448             }
7449         }
7450             
7451         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7452         return;
7453
7454     } else if (strncmp(message, "White resign", 12) == 0) {
7455         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7456         return;
7457     } else if (strncmp(message, "Black resign", 12) == 0) {
7458         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7459         return;
7460     } else if (strncmp(message, "White matches", 13) == 0 ||
7461                strncmp(message, "Black matches", 13) == 0   ) {
7462         /* [HGM] ignore GNUShogi noises */
7463         return;
7464     } else if (strncmp(message, "White", 5) == 0 &&
7465                message[5] != '(' &&
7466                StrStr(message, "Black") == NULL) {
7467         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7468         return;
7469     } else if (strncmp(message, "Black", 5) == 0 &&
7470                message[5] != '(') {
7471         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7472         return;
7473     } else if (strcmp(message, "resign") == 0 ||
7474                strcmp(message, "computer resigns") == 0) {
7475         switch (gameMode) {
7476           case MachinePlaysBlack:
7477           case IcsPlayingBlack:
7478             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7479             break;
7480           case MachinePlaysWhite:
7481           case IcsPlayingWhite:
7482             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7483             break;
7484           case TwoMachinesPlay:
7485             if (cps->twoMachinesColor[0] == 'w')
7486               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7487             else
7488               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7489             break;
7490           default:
7491             /* can't happen */
7492             break;
7493         }
7494         return;
7495     } else if (strncmp(message, "opponent mates", 14) == 0) {
7496         switch (gameMode) {
7497           case MachinePlaysBlack:
7498           case IcsPlayingBlack:
7499             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7500             break;
7501           case MachinePlaysWhite:
7502           case IcsPlayingWhite:
7503             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7504             break;
7505           case TwoMachinesPlay:
7506             if (cps->twoMachinesColor[0] == 'w')
7507               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7508             else
7509               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7510             break;
7511           default:
7512             /* can't happen */
7513             break;
7514         }
7515         return;
7516     } else if (strncmp(message, "computer mates", 14) == 0) {
7517         switch (gameMode) {
7518           case MachinePlaysBlack:
7519           case IcsPlayingBlack:
7520             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7521             break;
7522           case MachinePlaysWhite:
7523           case IcsPlayingWhite:
7524             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7525             break;
7526           case TwoMachinesPlay:
7527             if (cps->twoMachinesColor[0] == 'w')
7528               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7529             else
7530               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7531             break;
7532           default:
7533             /* can't happen */
7534             break;
7535         }
7536         return;
7537     } else if (strncmp(message, "checkmate", 9) == 0) {
7538         if (WhiteOnMove(forwardMostMove)) {
7539             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7540         } else {
7541             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7542         }
7543         return;
7544     } else if (strstr(message, "Draw") != NULL ||
7545                strstr(message, "game is a draw") != NULL) {
7546         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7547         return;
7548     } else if (strstr(message, "offer") != NULL &&
7549                strstr(message, "draw") != NULL) {
7550 #if ZIPPY
7551         if (appData.zippyPlay && first.initDone) {
7552             /* Relay offer to ICS */
7553             SendToICS(ics_prefix);
7554             SendToICS("draw\n");
7555         }
7556 #endif
7557         cps->offeredDraw = 2; /* valid until this engine moves twice */
7558         if (gameMode == TwoMachinesPlay) {
7559             if (cps->other->offeredDraw) {
7560                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7561             /* [HGM] in two-machine mode we delay relaying draw offer      */
7562             /* until after we also have move, to see if it is really claim */
7563             }
7564         } else if (gameMode == MachinePlaysWhite ||
7565                    gameMode == MachinePlaysBlack) {
7566           if (userOfferedDraw) {
7567             DisplayInformation(_("Machine accepts your draw offer"));
7568             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7569           } else {
7570             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7571           }
7572         }
7573     }
7574
7575     
7576     /*
7577      * Look for thinking output
7578      */
7579     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7580           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7581                                 ) {
7582         int plylev, mvleft, mvtot, curscore, time;
7583         char mvname[MOVE_LEN];
7584         u64 nodes; // [DM]
7585         char plyext;
7586         int ignore = FALSE;
7587         int prefixHint = FALSE;
7588         mvname[0] = NULLCHAR;
7589
7590         switch (gameMode) {
7591           case MachinePlaysBlack:
7592           case IcsPlayingBlack:
7593             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7594             break;
7595           case MachinePlaysWhite:
7596           case IcsPlayingWhite:
7597             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7598             break;
7599           case AnalyzeMode:
7600           case AnalyzeFile:
7601             break;
7602           case IcsObserving: /* [DM] icsEngineAnalyze */
7603             if (!appData.icsEngineAnalyze) ignore = TRUE;
7604             break;
7605           case TwoMachinesPlay:
7606             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7607                 ignore = TRUE;
7608             }
7609             break;
7610           default:
7611             ignore = TRUE;
7612             break;
7613         }
7614
7615         if (!ignore) {
7616             buf1[0] = NULLCHAR;
7617             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7618                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7619
7620                 if (plyext != ' ' && plyext != '\t') {
7621                     time *= 100;
7622                 }
7623
7624                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7625                 if( cps->scoreIsAbsolute && 
7626                     ( gameMode == MachinePlaysBlack ||
7627                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7628                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7629                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7630                      !WhiteOnMove(currentMove)
7631                     ) )
7632                 {
7633                     curscore = -curscore;
7634                 }
7635
7636
7637                 programStats.depth = plylev;
7638                 programStats.nodes = nodes;
7639                 programStats.time = time;
7640                 programStats.score = curscore;
7641                 programStats.got_only_move = 0;
7642
7643                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7644                         int ticklen;
7645
7646                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7647                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7648                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7649                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7650                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7651                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7652                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7653                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7654                 }
7655
7656                 /* Buffer overflow protection */
7657                 if (buf1[0] != NULLCHAR) {
7658                     if (strlen(buf1) >= sizeof(programStats.movelist)
7659                         && appData.debugMode) {
7660                         fprintf(debugFP,
7661                                 "PV is too long; using the first %u bytes.\n",
7662                                 (unsigned) sizeof(programStats.movelist) - 1);
7663                     }
7664
7665                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7666                 } else {
7667                     sprintf(programStats.movelist, " no PV\n");
7668                 }
7669
7670                 if (programStats.seen_stat) {
7671                     programStats.ok_to_send = 1;
7672                 }
7673
7674                 if (strchr(programStats.movelist, '(') != NULL) {
7675                     programStats.line_is_book = 1;
7676                     programStats.nr_moves = 0;
7677                     programStats.moves_left = 0;
7678                 } else {
7679                     programStats.line_is_book = 0;
7680                 }
7681
7682                 SendProgramStatsToFrontend( cps, &programStats );
7683
7684                 /* 
7685                     [AS] Protect the thinkOutput buffer from overflow... this
7686                     is only useful if buf1 hasn't overflowed first!
7687                 */
7688                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7689                         plylev, 
7690                         (gameMode == TwoMachinesPlay ?
7691                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7692                         ((double) curscore) / 100.0,
7693                         prefixHint ? lastHint : "",
7694                         prefixHint ? " " : "" );
7695
7696                 if( buf1[0] != NULLCHAR ) {
7697                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7698
7699                     if( strlen(buf1) > max_len ) {
7700                         if( appData.debugMode) {
7701                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7702                         }
7703                         buf1[max_len+1] = '\0';
7704                     }
7705
7706                     strcat( thinkOutput, buf1 );
7707                 }
7708
7709                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7710                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7711                     DisplayMove(currentMove - 1);
7712                 }
7713                 return;
7714
7715             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7716                 /* crafty (9.25+) says "(only move) <move>"
7717                  * if there is only 1 legal move
7718                  */
7719                 sscanf(p, "(only move) %s", buf1);
7720                 sprintf(thinkOutput, "%s (only move)", buf1);
7721                 sprintf(programStats.movelist, "%s (only move)", buf1);
7722                 programStats.depth = 1;
7723                 programStats.nr_moves = 1;
7724                 programStats.moves_left = 1;
7725                 programStats.nodes = 1;
7726                 programStats.time = 1;
7727                 programStats.got_only_move = 1;
7728
7729                 /* Not really, but we also use this member to
7730                    mean "line isn't going to change" (Crafty
7731                    isn't searching, so stats won't change) */
7732                 programStats.line_is_book = 1;
7733
7734                 SendProgramStatsToFrontend( cps, &programStats );
7735                 
7736                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7737                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7738                     DisplayMove(currentMove - 1);
7739                 }
7740                 return;
7741             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7742                               &time, &nodes, &plylev, &mvleft,
7743                               &mvtot, mvname) >= 5) {
7744                 /* The stat01: line is from Crafty (9.29+) in response
7745                    to the "." command */
7746                 programStats.seen_stat = 1;
7747                 cps->maybeThinking = TRUE;
7748
7749                 if (programStats.got_only_move || !appData.periodicUpdates)
7750                   return;
7751
7752                 programStats.depth = plylev;
7753                 programStats.time = time;
7754                 programStats.nodes = nodes;
7755                 programStats.moves_left = mvleft;
7756                 programStats.nr_moves = mvtot;
7757                 strcpy(programStats.move_name, mvname);
7758                 programStats.ok_to_send = 1;
7759                 programStats.movelist[0] = '\0';
7760
7761                 SendProgramStatsToFrontend( cps, &programStats );
7762
7763                 return;
7764
7765             } else if (strncmp(message,"++",2) == 0) {
7766                 /* Crafty 9.29+ outputs this */
7767                 programStats.got_fail = 2;
7768                 return;
7769
7770             } else if (strncmp(message,"--",2) == 0) {
7771                 /* Crafty 9.29+ outputs this */
7772                 programStats.got_fail = 1;
7773                 return;
7774
7775             } else if (thinkOutput[0] != NULLCHAR &&
7776                        strncmp(message, "    ", 4) == 0) {
7777                 unsigned message_len;
7778
7779                 p = message;
7780                 while (*p && *p == ' ') p++;
7781
7782                 message_len = strlen( p );
7783
7784                 /* [AS] Avoid buffer overflow */
7785                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7786                     strcat(thinkOutput, " ");
7787                     strcat(thinkOutput, p);
7788                 }
7789
7790                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7791                     strcat(programStats.movelist, " ");
7792                     strcat(programStats.movelist, p);
7793                 }
7794
7795                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7796                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7797                     DisplayMove(currentMove - 1);
7798                 }
7799                 return;
7800             }
7801         }
7802         else {
7803             buf1[0] = NULLCHAR;
7804
7805             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7806                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7807             {
7808                 ChessProgramStats cpstats;
7809
7810                 if (plyext != ' ' && plyext != '\t') {
7811                     time *= 100;
7812                 }
7813
7814                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7815                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7816                     curscore = -curscore;
7817                 }
7818
7819                 cpstats.depth = plylev;
7820                 cpstats.nodes = nodes;
7821                 cpstats.time = time;
7822                 cpstats.score = curscore;
7823                 cpstats.got_only_move = 0;
7824                 cpstats.movelist[0] = '\0';
7825
7826                 if (buf1[0] != NULLCHAR) {
7827                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7828                 }
7829
7830                 cpstats.ok_to_send = 0;
7831                 cpstats.line_is_book = 0;
7832                 cpstats.nr_moves = 0;
7833                 cpstats.moves_left = 0;
7834
7835                 SendProgramStatsToFrontend( cps, &cpstats );
7836             }
7837         }
7838     }
7839 }
7840
7841
7842 /* Parse a game score from the character string "game", and
7843    record it as the history of the current game.  The game
7844    score is NOT assumed to start from the standard position. 
7845    The display is not updated in any way.
7846    */
7847 void
7848 ParseGameHistory(game)
7849      char *game;
7850 {
7851     ChessMove moveType;
7852     int fromX, fromY, toX, toY, boardIndex;
7853     char promoChar;
7854     char *p, *q;
7855     char buf[MSG_SIZ];
7856
7857     if (appData.debugMode)
7858       fprintf(debugFP, "Parsing game history: %s\n", game);
7859
7860     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7861     gameInfo.site = StrSave(appData.icsHost);
7862     gameInfo.date = PGNDate();
7863     gameInfo.round = StrSave("-");
7864
7865     /* Parse out names of players */
7866     while (*game == ' ') game++;
7867     p = buf;
7868     while (*game != ' ') *p++ = *game++;
7869     *p = NULLCHAR;
7870     gameInfo.white = StrSave(buf);
7871     while (*game == ' ') game++;
7872     p = buf;
7873     while (*game != ' ' && *game != '\n') *p++ = *game++;
7874     *p = NULLCHAR;
7875     gameInfo.black = StrSave(buf);
7876
7877     /* Parse moves */
7878     boardIndex = blackPlaysFirst ? 1 : 0;
7879     yynewstr(game);
7880     for (;;) {
7881         yyboardindex = boardIndex;
7882         moveType = (ChessMove) yylex();
7883         switch (moveType) {
7884           case IllegalMove:             /* maybe suicide chess, etc. */
7885   if (appData.debugMode) {
7886     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7887     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7888     setbuf(debugFP, NULL);
7889   }
7890           case WhitePromotionChancellor:
7891           case BlackPromotionChancellor:
7892           case WhitePromotionArchbishop:
7893           case BlackPromotionArchbishop:
7894           case WhitePromotionQueen:
7895           case BlackPromotionQueen:
7896           case WhitePromotionRook:
7897           case BlackPromotionRook:
7898           case WhitePromotionBishop:
7899           case BlackPromotionBishop:
7900           case WhitePromotionKnight:
7901           case BlackPromotionKnight:
7902           case WhitePromotionKing:
7903           case BlackPromotionKing:
7904           case NormalMove:
7905           case WhiteCapturesEnPassant:
7906           case BlackCapturesEnPassant:
7907           case WhiteKingSideCastle:
7908           case WhiteQueenSideCastle:
7909           case BlackKingSideCastle:
7910           case BlackQueenSideCastle:
7911           case WhiteKingSideCastleWild:
7912           case WhiteQueenSideCastleWild:
7913           case BlackKingSideCastleWild:
7914           case BlackQueenSideCastleWild:
7915           /* PUSH Fabien */
7916           case WhiteHSideCastleFR:
7917           case WhiteASideCastleFR:
7918           case BlackHSideCastleFR:
7919           case BlackASideCastleFR:
7920           /* POP Fabien */
7921             fromX = currentMoveString[0] - AAA;
7922             fromY = currentMoveString[1] - ONE;
7923             toX = currentMoveString[2] - AAA;
7924             toY = currentMoveString[3] - ONE;
7925             promoChar = currentMoveString[4];
7926             break;
7927           case WhiteDrop:
7928           case BlackDrop:
7929             fromX = moveType == WhiteDrop ?
7930               (int) CharToPiece(ToUpper(currentMoveString[0])) :
7931             (int) CharToPiece(ToLower(currentMoveString[0]));
7932             fromY = DROP_RANK;
7933             toX = currentMoveString[2] - AAA;
7934             toY = currentMoveString[3] - ONE;
7935             promoChar = NULLCHAR;
7936             break;
7937           case AmbiguousMove:
7938             /* bug? */
7939             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7940   if (appData.debugMode) {
7941     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7942     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7943     setbuf(debugFP, NULL);
7944   }
7945             DisplayError(buf, 0);
7946             return;
7947           case ImpossibleMove:
7948             /* bug? */
7949             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7950   if (appData.debugMode) {
7951     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7952     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7953     setbuf(debugFP, NULL);
7954   }
7955             DisplayError(buf, 0);
7956             return;
7957           case (ChessMove) 0:   /* end of file */
7958             if (boardIndex < backwardMostMove) {
7959                 /* Oops, gap.  How did that happen? */
7960                 DisplayError(_("Gap in move list"), 0);
7961                 return;
7962             }
7963             backwardMostMove =  blackPlaysFirst ? 1 : 0;
7964             if (boardIndex > forwardMostMove) {
7965                 forwardMostMove = boardIndex;
7966             }
7967             return;
7968           case ElapsedTime:
7969             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7970                 strcat(parseList[boardIndex-1], " ");
7971                 strcat(parseList[boardIndex-1], yy_text);
7972             }
7973             continue;
7974           case Comment:
7975           case PGNTag:
7976           case NAG:
7977           default:
7978             /* ignore */
7979             continue;
7980           case WhiteWins:
7981           case BlackWins:
7982           case GameIsDrawn:
7983           case GameUnfinished:
7984             if (gameMode == IcsExamining) {
7985                 if (boardIndex < backwardMostMove) {
7986                     /* Oops, gap.  How did that happen? */
7987                     return;
7988                 }
7989                 backwardMostMove = blackPlaysFirst ? 1 : 0;
7990                 return;
7991             }
7992             gameInfo.result = moveType;
7993             p = strchr(yy_text, '{');
7994             if (p == NULL) p = strchr(yy_text, '(');
7995             if (p == NULL) {
7996                 p = yy_text;
7997                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7998             } else {
7999                 q = strchr(p, *p == '{' ? '}' : ')');
8000                 if (q != NULL) *q = NULLCHAR;
8001                 p++;
8002             }
8003             gameInfo.resultDetails = StrSave(p);
8004             continue;
8005         }
8006         if (boardIndex >= forwardMostMove &&
8007             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8008             backwardMostMove = blackPlaysFirst ? 1 : 0;
8009             return;
8010         }
8011         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8012                                  fromY, fromX, toY, toX, promoChar,
8013                                  parseList[boardIndex]);
8014         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8015         /* currentMoveString is set as a side-effect of yylex */
8016         strcpy(moveList[boardIndex], currentMoveString);
8017         strcat(moveList[boardIndex], "\n");
8018         boardIndex++;
8019         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8020         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8021           case MT_NONE:
8022           case MT_STALEMATE:
8023           default:
8024             break;
8025           case MT_CHECK:
8026             if(gameInfo.variant != VariantShogi)
8027                 strcat(parseList[boardIndex - 1], "+");
8028             break;
8029           case MT_CHECKMATE:
8030           case MT_STAINMATE:
8031             strcat(parseList[boardIndex - 1], "#");
8032             break;
8033         }
8034     }
8035 }
8036
8037
8038 /* Apply a move to the given board  */
8039 void
8040 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8041      int fromX, fromY, toX, toY;
8042      int promoChar;
8043      Board board;
8044 {
8045   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8046   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8047
8048     /* [HGM] compute & store e.p. status and castling rights for new position */
8049     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8050     { int i;
8051
8052       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8053       oldEP = (signed char)board[EP_STATUS];
8054       board[EP_STATUS] = EP_NONE;
8055
8056       if( board[toY][toX] != EmptySquare ) 
8057            board[EP_STATUS] = EP_CAPTURE;  
8058
8059       if( board[fromY][fromX] == WhitePawn ) {
8060            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8061                board[EP_STATUS] = EP_PAWN_MOVE;
8062            if( toY-fromY==2) {
8063                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8064                         gameInfo.variant != VariantBerolina || toX < fromX)
8065                       board[EP_STATUS] = toX | berolina;
8066                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8067                         gameInfo.variant != VariantBerolina || toX > fromX) 
8068                       board[EP_STATUS] = toX;
8069            }
8070       } else 
8071       if( board[fromY][fromX] == BlackPawn ) {
8072            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8073                board[EP_STATUS] = EP_PAWN_MOVE; 
8074            if( toY-fromY== -2) {
8075                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8076                         gameInfo.variant != VariantBerolina || toX < fromX)
8077                       board[EP_STATUS] = toX | berolina;
8078                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8079                         gameInfo.variant != VariantBerolina || toX > fromX) 
8080                       board[EP_STATUS] = toX;
8081            }
8082        }
8083
8084        for(i=0; i<nrCastlingRights; i++) {
8085            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8086               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8087              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8088        }
8089
8090     }
8091
8092   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8093   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8094        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8095          
8096   if (fromX == toX && fromY == toY) return;
8097
8098   if (fromY == DROP_RANK) {
8099         /* must be first */
8100         piece = board[toY][toX] = (ChessSquare) fromX;
8101   } else {
8102      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8103      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8104      if(gameInfo.variant == VariantKnightmate)
8105          king += (int) WhiteUnicorn - (int) WhiteKing;
8106
8107     /* Code added by Tord: */
8108     /* FRC castling assumed when king captures friendly rook. */
8109     if (board[fromY][fromX] == WhiteKing &&
8110              board[toY][toX] == WhiteRook) {
8111       board[fromY][fromX] = EmptySquare;
8112       board[toY][toX] = EmptySquare;
8113       if(toX > fromX) {
8114         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8115       } else {
8116         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8117       }
8118     } else if (board[fromY][fromX] == BlackKing &&
8119                board[toY][toX] == BlackRook) {
8120       board[fromY][fromX] = EmptySquare;
8121       board[toY][toX] = EmptySquare;
8122       if(toX > fromX) {
8123         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8124       } else {
8125         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8126       }
8127     /* End of code added by Tord */
8128
8129     } else if (board[fromY][fromX] == king
8130         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8131         && toY == fromY && toX > fromX+1) {
8132         board[fromY][fromX] = EmptySquare;
8133         board[toY][toX] = king;
8134         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8135         board[fromY][BOARD_RGHT-1] = EmptySquare;
8136     } else if (board[fromY][fromX] == king
8137         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8138                && toY == fromY && toX < fromX-1) {
8139         board[fromY][fromX] = EmptySquare;
8140         board[toY][toX] = king;
8141         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8142         board[fromY][BOARD_LEFT] = EmptySquare;
8143     } else if (board[fromY][fromX] == WhitePawn
8144                && toY >= BOARD_HEIGHT-promoRank
8145                && gameInfo.variant != VariantXiangqi
8146                ) {
8147         /* white pawn promotion */
8148         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8149         if (board[toY][toX] == EmptySquare) {
8150             board[toY][toX] = WhiteQueen;
8151         }
8152         if(gameInfo.variant==VariantBughouse ||
8153            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8155         board[fromY][fromX] = EmptySquare;
8156     } else if ((fromY == BOARD_HEIGHT-4)
8157                && (toX != fromX)
8158                && gameInfo.variant != VariantXiangqi
8159                && gameInfo.variant != VariantBerolina
8160                && (board[fromY][fromX] == WhitePawn)
8161                && (board[toY][toX] == EmptySquare)) {
8162         board[fromY][fromX] = EmptySquare;
8163         board[toY][toX] = WhitePawn;
8164         captured = board[toY - 1][toX];
8165         board[toY - 1][toX] = EmptySquare;
8166     } else if ((fromY == BOARD_HEIGHT-4)
8167                && (toX == fromX)
8168                && gameInfo.variant == VariantBerolina
8169                && (board[fromY][fromX] == WhitePawn)
8170                && (board[toY][toX] == EmptySquare)) {
8171         board[fromY][fromX] = EmptySquare;
8172         board[toY][toX] = WhitePawn;
8173         if(oldEP & EP_BEROLIN_A) {
8174                 captured = board[fromY][fromX-1];
8175                 board[fromY][fromX-1] = EmptySquare;
8176         }else{  captured = board[fromY][fromX+1];
8177                 board[fromY][fromX+1] = EmptySquare;
8178         }
8179     } else if (board[fromY][fromX] == king
8180         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8181                && toY == fromY && toX > fromX+1) {
8182         board[fromY][fromX] = EmptySquare;
8183         board[toY][toX] = king;
8184         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8185         board[fromY][BOARD_RGHT-1] = EmptySquare;
8186     } else if (board[fromY][fromX] == king
8187         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8188                && toY == fromY && toX < fromX-1) {
8189         board[fromY][fromX] = EmptySquare;
8190         board[toY][toX] = king;
8191         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8192         board[fromY][BOARD_LEFT] = EmptySquare;
8193     } else if (fromY == 7 && fromX == 3
8194                && board[fromY][fromX] == BlackKing
8195                && toY == 7 && toX == 5) {
8196         board[fromY][fromX] = EmptySquare;
8197         board[toY][toX] = BlackKing;
8198         board[fromY][7] = EmptySquare;
8199         board[toY][4] = BlackRook;
8200     } else if (fromY == 7 && fromX == 3
8201                && board[fromY][fromX] == BlackKing
8202                && toY == 7 && toX == 1) {
8203         board[fromY][fromX] = EmptySquare;
8204         board[toY][toX] = BlackKing;
8205         board[fromY][0] = EmptySquare;
8206         board[toY][2] = BlackRook;
8207     } else if (board[fromY][fromX] == BlackPawn
8208                && toY < promoRank
8209                && gameInfo.variant != VariantXiangqi
8210                ) {
8211         /* black pawn promotion */
8212         board[toY][toX] = CharToPiece(ToLower(promoChar));
8213         if (board[toY][toX] == EmptySquare) {
8214             board[toY][toX] = BlackQueen;
8215         }
8216         if(gameInfo.variant==VariantBughouse ||
8217            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8218             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8219         board[fromY][fromX] = EmptySquare;
8220     } else if ((fromY == 3)
8221                && (toX != fromX)
8222                && gameInfo.variant != VariantXiangqi
8223                && gameInfo.variant != VariantBerolina
8224                && (board[fromY][fromX] == BlackPawn)
8225                && (board[toY][toX] == EmptySquare)) {
8226         board[fromY][fromX] = EmptySquare;
8227         board[toY][toX] = BlackPawn;
8228         captured = board[toY + 1][toX];
8229         board[toY + 1][toX] = EmptySquare;
8230     } else if ((fromY == 3)
8231                && (toX == fromX)
8232                && gameInfo.variant == VariantBerolina
8233                && (board[fromY][fromX] == BlackPawn)
8234                && (board[toY][toX] == EmptySquare)) {
8235         board[fromY][fromX] = EmptySquare;
8236         board[toY][toX] = BlackPawn;
8237         if(oldEP & EP_BEROLIN_A) {
8238                 captured = board[fromY][fromX-1];
8239                 board[fromY][fromX-1] = EmptySquare;
8240         }else{  captured = board[fromY][fromX+1];
8241                 board[fromY][fromX+1] = EmptySquare;
8242         }
8243     } else {
8244         board[toY][toX] = board[fromY][fromX];
8245         board[fromY][fromX] = EmptySquare;
8246     }
8247
8248     /* [HGM] now we promote for Shogi, if needed */
8249     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8250         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8251   }
8252
8253     if (gameInfo.holdingsWidth != 0) {
8254
8255       /* !!A lot more code needs to be written to support holdings  */
8256       /* [HGM] OK, so I have written it. Holdings are stored in the */
8257       /* penultimate board files, so they are automaticlly stored   */
8258       /* in the game history.                                       */
8259       if (fromY == DROP_RANK) {
8260         /* Delete from holdings, by decreasing count */
8261         /* and erasing image if necessary            */
8262         p = (int) fromX;
8263         if(p < (int) BlackPawn) { /* white drop */
8264              p -= (int)WhitePawn;
8265                  p = PieceToNumber((ChessSquare)p);
8266              if(p >= gameInfo.holdingsSize) p = 0;
8267              if(--board[p][BOARD_WIDTH-2] <= 0)
8268                   board[p][BOARD_WIDTH-1] = EmptySquare;
8269              if((int)board[p][BOARD_WIDTH-2] < 0)
8270                         board[p][BOARD_WIDTH-2] = 0;
8271         } else {                  /* black drop */
8272              p -= (int)BlackPawn;
8273                  p = PieceToNumber((ChessSquare)p);
8274              if(p >= gameInfo.holdingsSize) p = 0;
8275              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8276                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8277              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8278                         board[BOARD_HEIGHT-1-p][1] = 0;
8279         }
8280       }
8281       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8282           && gameInfo.variant != VariantBughouse        ) {
8283         /* [HGM] holdings: Add to holdings, if holdings exist */
8284         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8285                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8286                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8287         }
8288         p = (int) captured;
8289         if (p >= (int) BlackPawn) {
8290           p -= (int)BlackPawn;
8291           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8292                   /* in Shogi restore piece to its original  first */
8293                   captured = (ChessSquare) (DEMOTED captured);
8294                   p = DEMOTED p;
8295           }
8296           p = PieceToNumber((ChessSquare)p);
8297           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8298           board[p][BOARD_WIDTH-2]++;
8299           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8300         } else {
8301           p -= (int)WhitePawn;
8302           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8303                   captured = (ChessSquare) (DEMOTED captured);
8304                   p = DEMOTED p;
8305           }
8306           p = PieceToNumber((ChessSquare)p);
8307           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8308           board[BOARD_HEIGHT-1-p][1]++;
8309           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8310         }
8311       }
8312     } else if (gameInfo.variant == VariantAtomic) {
8313       if (captured != EmptySquare) {
8314         int y, x;
8315         for (y = toY-1; y <= toY+1; y++) {
8316           for (x = toX-1; x <= toX+1; x++) {
8317             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8318                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8319               board[y][x] = EmptySquare;
8320             }
8321           }
8322         }
8323         board[toY][toX] = EmptySquare;
8324       }
8325     }
8326     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8327         /* [HGM] Shogi promotions */
8328         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8329     }
8330
8331     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8332                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8333         // [HGM] superchess: take promotion piece out of holdings
8334         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8335         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8336             if(!--board[k][BOARD_WIDTH-2])
8337                 board[k][BOARD_WIDTH-1] = EmptySquare;
8338         } else {
8339             if(!--board[BOARD_HEIGHT-1-k][1])
8340                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8341         }
8342     }
8343
8344 }
8345
8346 /* Updates forwardMostMove */
8347 void
8348 MakeMove(fromX, fromY, toX, toY, promoChar)
8349      int fromX, fromY, toX, toY;
8350      int promoChar;
8351 {
8352 //    forwardMostMove++; // [HGM] bare: moved downstream
8353
8354     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8355         int timeLeft; static int lastLoadFlag=0; int king, piece;
8356         piece = boards[forwardMostMove][fromY][fromX];
8357         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8358         if(gameInfo.variant == VariantKnightmate)
8359             king += (int) WhiteUnicorn - (int) WhiteKing;
8360         if(forwardMostMove == 0) {
8361             if(blackPlaysFirst) 
8362                 fprintf(serverMoves, "%s;", second.tidy);
8363             fprintf(serverMoves, "%s;", first.tidy);
8364             if(!blackPlaysFirst) 
8365                 fprintf(serverMoves, "%s;", second.tidy);
8366         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8367         lastLoadFlag = loadFlag;
8368         // print base move
8369         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8370         // print castling suffix
8371         if( toY == fromY && piece == king ) {
8372             if(toX-fromX > 1)
8373                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8374             if(fromX-toX >1)
8375                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8376         }
8377         // e.p. suffix
8378         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8379              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8380              boards[forwardMostMove][toY][toX] == EmptySquare
8381              && fromX != toX )
8382                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8383         // promotion suffix
8384         if(promoChar != NULLCHAR)
8385                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8386         if(!loadFlag) {
8387             fprintf(serverMoves, "/%d/%d",
8388                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8389             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8390             else                      timeLeft = blackTimeRemaining/1000;
8391             fprintf(serverMoves, "/%d", timeLeft);
8392         }
8393         fflush(serverMoves);
8394     }
8395
8396     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8397       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8398                         0, 1);
8399       return;
8400     }
8401     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8402     if (commentList[forwardMostMove+1] != NULL) {
8403         free(commentList[forwardMostMove+1]);
8404         commentList[forwardMostMove+1] = NULL;
8405     }
8406     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8407     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8408     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8409     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8410     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8411     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8412     gameInfo.result = GameUnfinished;
8413     if (gameInfo.resultDetails != NULL) {
8414         free(gameInfo.resultDetails);
8415         gameInfo.resultDetails = NULL;
8416     }
8417     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8418                               moveList[forwardMostMove - 1]);
8419     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8420                              PosFlags(forwardMostMove - 1),
8421                              fromY, fromX, toY, toX, promoChar,
8422                              parseList[forwardMostMove - 1]);
8423     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8424       case MT_NONE:
8425       case MT_STALEMATE:
8426       default:
8427         break;
8428       case MT_CHECK:
8429         if(gameInfo.variant != VariantShogi)
8430             strcat(parseList[forwardMostMove - 1], "+");
8431         break;
8432       case MT_CHECKMATE:
8433       case MT_STAINMATE:
8434         strcat(parseList[forwardMostMove - 1], "#");
8435         break;
8436     }
8437     if (appData.debugMode) {
8438         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8439     }
8440
8441 }
8442
8443 /* Updates currentMove if not pausing */
8444 void
8445 ShowMove(fromX, fromY, toX, toY)
8446 {
8447     int instant = (gameMode == PlayFromGameFile) ?
8448         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8449     if(appData.noGUI) return;
8450     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8451         if (!instant) {
8452             if (forwardMostMove == currentMove + 1) {
8453                 AnimateMove(boards[forwardMostMove - 1],
8454                             fromX, fromY, toX, toY);
8455             }
8456             if (appData.highlightLastMove) {
8457                 SetHighlights(fromX, fromY, toX, toY);
8458             }
8459         }
8460         currentMove = forwardMostMove;
8461     }
8462
8463     if (instant) return;
8464
8465     DisplayMove(currentMove - 1);
8466     DrawPosition(FALSE, boards[currentMove]);
8467     DisplayBothClocks();
8468     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8469 }
8470
8471 void SendEgtPath(ChessProgramState *cps)
8472 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8473         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8474
8475         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8476
8477         while(*p) {
8478             char c, *q = name+1, *r, *s;
8479
8480             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8481             while(*p && *p != ',') *q++ = *p++;
8482             *q++ = ':'; *q = 0;
8483             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8484                 strcmp(name, ",nalimov:") == 0 ) {
8485                 // take nalimov path from the menu-changeable option first, if it is defined
8486                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8487                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8488             } else
8489             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8490                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8491                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8492                 s = r = StrStr(s, ":") + 1; // beginning of path info
8493                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8494                 c = *r; *r = 0;             // temporarily null-terminate path info
8495                     *--q = 0;               // strip of trailig ':' from name
8496                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8497                 *r = c;
8498                 SendToProgram(buf,cps);     // send egtbpath command for this format
8499             }
8500             if(*p == ',') p++; // read away comma to position for next format name
8501         }
8502 }
8503
8504 void
8505 InitChessProgram(cps, setup)
8506      ChessProgramState *cps;
8507      int setup; /* [HGM] needed to setup FRC opening position */
8508 {
8509     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8510     if (appData.noChessProgram) return;
8511     hintRequested = FALSE;
8512     bookRequested = FALSE;
8513
8514     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8515     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8516     if(cps->memSize) { /* [HGM] memory */
8517         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8518         SendToProgram(buf, cps);
8519     }
8520     SendEgtPath(cps); /* [HGM] EGT */
8521     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8522         sprintf(buf, "cores %d\n", appData.smpCores);
8523         SendToProgram(buf, cps);
8524     }
8525
8526     SendToProgram(cps->initString, cps);
8527     if (gameInfo.variant != VariantNormal &&
8528         gameInfo.variant != VariantLoadable
8529         /* [HGM] also send variant if board size non-standard */
8530         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8531                                             ) {
8532       char *v = VariantName(gameInfo.variant);
8533       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8534         /* [HGM] in protocol 1 we have to assume all variants valid */
8535         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8536         DisplayFatalError(buf, 0, 1);
8537         return;
8538       }
8539
8540       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8541       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8542       if( gameInfo.variant == VariantXiangqi )
8543            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8544       if( gameInfo.variant == VariantShogi )
8545            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8546       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8547            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8548       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8549                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8550            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8551       if( gameInfo.variant == VariantCourier )
8552            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8553       if( gameInfo.variant == VariantSuper )
8554            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8555       if( gameInfo.variant == VariantGreat )
8556            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8557
8558       if(overruled) {
8559            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8560                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8561            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8562            if(StrStr(cps->variants, b) == NULL) { 
8563                // specific sized variant not known, check if general sizing allowed
8564                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8565                    if(StrStr(cps->variants, "boardsize") == NULL) {
8566                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8567                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8568                        DisplayFatalError(buf, 0, 1);
8569                        return;
8570                    }
8571                    /* [HGM] here we really should compare with the maximum supported board size */
8572                }
8573            }
8574       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8575       sprintf(buf, "variant %s\n", b);
8576       SendToProgram(buf, cps);
8577     }
8578     currentlyInitializedVariant = gameInfo.variant;
8579
8580     /* [HGM] send opening position in FRC to first engine */
8581     if(setup) {
8582           SendToProgram("force\n", cps);
8583           SendBoard(cps, 0);
8584           /* engine is now in force mode! Set flag to wake it up after first move. */
8585           setboardSpoiledMachineBlack = 1;
8586     }
8587
8588     if (cps->sendICS) {
8589       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8590       SendToProgram(buf, cps);
8591     }
8592     cps->maybeThinking = FALSE;
8593     cps->offeredDraw = 0;
8594     if (!appData.icsActive) {
8595         SendTimeControl(cps, movesPerSession, timeControl,
8596                         timeIncrement, appData.searchDepth,
8597                         searchTime);
8598     }
8599     if (appData.showThinking 
8600         // [HGM] thinking: four options require thinking output to be sent
8601         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8602                                 ) {
8603         SendToProgram("post\n", cps);
8604     }
8605     SendToProgram("hard\n", cps);
8606     if (!appData.ponderNextMove) {
8607         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8608            it without being sure what state we are in first.  "hard"
8609            is not a toggle, so that one is OK.
8610          */
8611         SendToProgram("easy\n", cps);
8612     }
8613     if (cps->usePing) {
8614       sprintf(buf, "ping %d\n", ++cps->lastPing);
8615       SendToProgram(buf, cps);
8616     }
8617     cps->initDone = TRUE;
8618 }   
8619
8620
8621 void
8622 StartChessProgram(cps)
8623      ChessProgramState *cps;
8624 {
8625     char buf[MSG_SIZ];
8626     int err;
8627
8628     if (appData.noChessProgram) return;
8629     cps->initDone = FALSE;
8630
8631     if (strcmp(cps->host, "localhost") == 0) {
8632         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8633     } else if (*appData.remoteShell == NULLCHAR) {
8634         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8635     } else {
8636         if (*appData.remoteUser == NULLCHAR) {
8637           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8638                     cps->program);
8639         } else {
8640           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8641                     cps->host, appData.remoteUser, cps->program);
8642         }
8643         err = StartChildProcess(buf, "", &cps->pr);
8644     }
8645     
8646     if (err != 0) {
8647         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8648         DisplayFatalError(buf, err, 1);
8649         cps->pr = NoProc;
8650         cps->isr = NULL;
8651         return;
8652     }
8653     
8654     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8655     if (cps->protocolVersion > 1) {
8656       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8657       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8658       cps->comboCnt = 0;  //                and values of combo boxes
8659       SendToProgram(buf, cps);
8660     } else {
8661       SendToProgram("xboard\n", cps);
8662     }
8663 }
8664
8665
8666 void
8667 TwoMachinesEventIfReady P((void))
8668 {
8669   if (first.lastPing != first.lastPong) {
8670     DisplayMessage("", _("Waiting for first chess program"));
8671     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8672     return;
8673   }
8674   if (second.lastPing != second.lastPong) {
8675     DisplayMessage("", _("Waiting for second chess program"));
8676     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8677     return;
8678   }
8679   ThawUI();
8680   TwoMachinesEvent();
8681 }
8682
8683 void
8684 NextMatchGame P((void))
8685 {
8686     int index; /* [HGM] autoinc: step load index during match */
8687     Reset(FALSE, TRUE);
8688     if (*appData.loadGameFile != NULLCHAR) {
8689         index = appData.loadGameIndex;
8690         if(index < 0) { // [HGM] autoinc
8691             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8692             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8693         } 
8694         LoadGameFromFile(appData.loadGameFile,
8695                          index,
8696                          appData.loadGameFile, FALSE);
8697     } else if (*appData.loadPositionFile != NULLCHAR) {
8698         index = appData.loadPositionIndex;
8699         if(index < 0) { // [HGM] autoinc
8700             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8701             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8702         } 
8703         LoadPositionFromFile(appData.loadPositionFile,
8704                              index,
8705                              appData.loadPositionFile);
8706     }
8707     TwoMachinesEventIfReady();
8708 }
8709
8710 void UserAdjudicationEvent( int result )
8711 {
8712     ChessMove gameResult = GameIsDrawn;
8713
8714     if( result > 0 ) {
8715         gameResult = WhiteWins;
8716     }
8717     else if( result < 0 ) {
8718         gameResult = BlackWins;
8719     }
8720
8721     if( gameMode == TwoMachinesPlay ) {
8722         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8723     }
8724 }
8725
8726
8727 // [HGM] save: calculate checksum of game to make games easily identifiable
8728 int StringCheckSum(char *s)
8729 {
8730         int i = 0;
8731         if(s==NULL) return 0;
8732         while(*s) i = i*259 + *s++;
8733         return i;
8734 }
8735
8736 int GameCheckSum()
8737 {
8738         int i, sum=0;
8739         for(i=backwardMostMove; i<forwardMostMove; i++) {
8740                 sum += pvInfoList[i].depth;
8741                 sum += StringCheckSum(parseList[i]);
8742                 sum += StringCheckSum(commentList[i]);
8743                 sum *= 261;
8744         }
8745         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8746         return sum + StringCheckSum(commentList[i]);
8747 } // end of save patch
8748
8749 void
8750 GameEnds(result, resultDetails, whosays)
8751      ChessMove result;
8752      char *resultDetails;
8753      int whosays;
8754 {
8755     GameMode nextGameMode;
8756     int isIcsGame;
8757     char buf[MSG_SIZ];
8758
8759     if(endingGame) return; /* [HGM] crash: forbid recursion */
8760     endingGame = 1;
8761
8762     if (appData.debugMode) {
8763       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8764               result, resultDetails ? resultDetails : "(null)", whosays);
8765     }
8766
8767     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8768
8769     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8770         /* If we are playing on ICS, the server decides when the
8771            game is over, but the engine can offer to draw, claim 
8772            a draw, or resign. 
8773          */
8774 #if ZIPPY
8775         if (appData.zippyPlay && first.initDone) {
8776             if (result == GameIsDrawn) {
8777                 /* In case draw still needs to be claimed */
8778                 SendToICS(ics_prefix);
8779                 SendToICS("draw\n");
8780             } else if (StrCaseStr(resultDetails, "resign")) {
8781                 SendToICS(ics_prefix);
8782                 SendToICS("resign\n");
8783             }
8784         }
8785 #endif
8786         endingGame = 0; /* [HGM] crash */
8787         return;
8788     }
8789
8790     /* If we're loading the game from a file, stop */
8791     if (whosays == GE_FILE) {
8792       (void) StopLoadGameTimer();
8793       gameFileFP = NULL;
8794     }
8795
8796     /* Cancel draw offers */
8797     first.offeredDraw = second.offeredDraw = 0;
8798
8799     /* If this is an ICS game, only ICS can really say it's done;
8800        if not, anyone can. */
8801     isIcsGame = (gameMode == IcsPlayingWhite || 
8802                  gameMode == IcsPlayingBlack || 
8803                  gameMode == IcsObserving    || 
8804                  gameMode == IcsExamining);
8805
8806     if (!isIcsGame || whosays == GE_ICS) {
8807         /* OK -- not an ICS game, or ICS said it was done */
8808         StopClocks();
8809         if (!isIcsGame && !appData.noChessProgram) 
8810           SetUserThinkingEnables();
8811     
8812         /* [HGM] if a machine claims the game end we verify this claim */
8813         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8814             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8815                 char claimer;
8816                 ChessMove trueResult = (ChessMove) -1;
8817
8818                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8819                                             first.twoMachinesColor[0] :
8820                                             second.twoMachinesColor[0] ;
8821
8822                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8823                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8824                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8825                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8826                 } else
8827                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8828                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8829                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8830                 } else
8831                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8832                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8833                 }
8834
8835                 // now verify win claims, but not in drop games, as we don't understand those yet
8836                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8837                                                  || gameInfo.variant == VariantGreat) &&
8838                     (result == WhiteWins && claimer == 'w' ||
8839                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8840                       if (appData.debugMode) {
8841                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8842                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8843                       }
8844                       if(result != trueResult) {
8845                               sprintf(buf, "False win claim: '%s'", resultDetails);
8846                               result = claimer == 'w' ? BlackWins : WhiteWins;
8847                               resultDetails = buf;
8848                       }
8849                 } else
8850                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8851                     && (forwardMostMove <= backwardMostMove ||
8852                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8853                         (claimer=='b')==(forwardMostMove&1))
8854                                                                                   ) {
8855                       /* [HGM] verify: draws that were not flagged are false claims */
8856                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8857                       result = claimer == 'w' ? BlackWins : WhiteWins;
8858                       resultDetails = buf;
8859                 }
8860                 /* (Claiming a loss is accepted no questions asked!) */
8861             }
8862             /* [HGM] bare: don't allow bare King to win */
8863             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8864                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8865                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8866                && result != GameIsDrawn)
8867             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8868                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8869                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8870                         if(p >= 0 && p <= (int)WhiteKing) k++;
8871                 }
8872                 if (appData.debugMode) {
8873                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8874                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8875                 }
8876                 if(k <= 1) {
8877                         result = GameIsDrawn;
8878                         sprintf(buf, "%s but bare king", resultDetails);
8879                         resultDetails = buf;
8880                 }
8881             }
8882         }
8883
8884
8885         if(serverMoves != NULL && !loadFlag) { char c = '=';
8886             if(result==WhiteWins) c = '+';
8887             if(result==BlackWins) c = '-';
8888             if(resultDetails != NULL)
8889                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8890         }
8891         if (resultDetails != NULL) {
8892             gameInfo.result = result;
8893             gameInfo.resultDetails = StrSave(resultDetails);
8894
8895             /* display last move only if game was not loaded from file */
8896             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8897                 DisplayMove(currentMove - 1);
8898     
8899             if (forwardMostMove != 0) {
8900                 if (gameMode != PlayFromGameFile && gameMode != EditGame
8901                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8902                                                                 ) {
8903                     if (*appData.saveGameFile != NULLCHAR) {
8904                         SaveGameToFile(appData.saveGameFile, TRUE);
8905                     } else if (appData.autoSaveGames) {
8906                         AutoSaveGame();
8907                     }
8908                     if (*appData.savePositionFile != NULLCHAR) {
8909                         SavePositionToFile(appData.savePositionFile);
8910                     }
8911                 }
8912             }
8913
8914             /* Tell program how game ended in case it is learning */
8915             /* [HGM] Moved this to after saving the PGN, just in case */
8916             /* engine died and we got here through time loss. In that */
8917             /* case we will get a fatal error writing the pipe, which */
8918             /* would otherwise lose us the PGN.                       */
8919             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
8920             /* output during GameEnds should never be fatal anymore   */
8921             if (gameMode == MachinePlaysWhite ||
8922                 gameMode == MachinePlaysBlack ||
8923                 gameMode == TwoMachinesPlay ||
8924                 gameMode == IcsPlayingWhite ||
8925                 gameMode == IcsPlayingBlack ||
8926                 gameMode == BeginningOfGame) {
8927                 char buf[MSG_SIZ];
8928                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8929                         resultDetails);
8930                 if (first.pr != NoProc) {
8931                     SendToProgram(buf, &first);
8932                 }
8933                 if (second.pr != NoProc &&
8934                     gameMode == TwoMachinesPlay) {
8935                     SendToProgram(buf, &second);
8936                 }
8937             }
8938         }
8939
8940         if (appData.icsActive) {
8941             if (appData.quietPlay &&
8942                 (gameMode == IcsPlayingWhite ||
8943                  gameMode == IcsPlayingBlack)) {
8944                 SendToICS(ics_prefix);
8945                 SendToICS("set shout 1\n");
8946             }
8947             nextGameMode = IcsIdle;
8948             ics_user_moved = FALSE;
8949             /* clean up premove.  It's ugly when the game has ended and the
8950              * premove highlights are still on the board.
8951              */
8952             if (gotPremove) {
8953               gotPremove = FALSE;
8954               ClearPremoveHighlights();
8955               DrawPosition(FALSE, boards[currentMove]);
8956             }
8957             if (whosays == GE_ICS) {
8958                 switch (result) {
8959                 case WhiteWins:
8960                     if (gameMode == IcsPlayingWhite)
8961                         PlayIcsWinSound();
8962                     else if(gameMode == IcsPlayingBlack)
8963                         PlayIcsLossSound();
8964                     break;
8965                 case BlackWins:
8966                     if (gameMode == IcsPlayingBlack)
8967                         PlayIcsWinSound();
8968                     else if(gameMode == IcsPlayingWhite)
8969                         PlayIcsLossSound();
8970                     break;
8971                 case GameIsDrawn:
8972                     PlayIcsDrawSound();
8973                     break;
8974                 default:
8975                     PlayIcsUnfinishedSound();
8976                 }
8977             }
8978         } else if (gameMode == EditGame ||
8979                    gameMode == PlayFromGameFile || 
8980                    gameMode == AnalyzeMode || 
8981                    gameMode == AnalyzeFile) {
8982             nextGameMode = gameMode;
8983         } else {
8984             nextGameMode = EndOfGame;
8985         }
8986         pausing = FALSE;
8987         ModeHighlight();
8988     } else {
8989         nextGameMode = gameMode;
8990     }
8991
8992     if (appData.noChessProgram) {
8993         gameMode = nextGameMode;
8994         ModeHighlight();
8995         endingGame = 0; /* [HGM] crash */
8996         return;
8997     }
8998
8999     if (first.reuse) {
9000         /* Put first chess program into idle state */
9001         if (first.pr != NoProc &&
9002             (gameMode == MachinePlaysWhite ||
9003              gameMode == MachinePlaysBlack ||
9004              gameMode == TwoMachinesPlay ||
9005              gameMode == IcsPlayingWhite ||
9006              gameMode == IcsPlayingBlack ||
9007              gameMode == BeginningOfGame)) {
9008             SendToProgram("force\n", &first);
9009             if (first.usePing) {
9010               char buf[MSG_SIZ];
9011               sprintf(buf, "ping %d\n", ++first.lastPing);
9012               SendToProgram(buf, &first);
9013             }
9014         }
9015     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9016         /* Kill off first chess program */
9017         if (first.isr != NULL)
9018           RemoveInputSource(first.isr);
9019         first.isr = NULL;
9020     
9021         if (first.pr != NoProc) {
9022             ExitAnalyzeMode();
9023             DoSleep( appData.delayBeforeQuit );
9024             SendToProgram("quit\n", &first);
9025             DoSleep( appData.delayAfterQuit );
9026             DestroyChildProcess(first.pr, first.useSigterm);
9027         }
9028         first.pr = NoProc;
9029     }
9030     if (second.reuse) {
9031         /* Put second chess program into idle state */
9032         if (second.pr != NoProc &&
9033             gameMode == TwoMachinesPlay) {
9034             SendToProgram("force\n", &second);
9035             if (second.usePing) {
9036               char buf[MSG_SIZ];
9037               sprintf(buf, "ping %d\n", ++second.lastPing);
9038               SendToProgram(buf, &second);
9039             }
9040         }
9041     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9042         /* Kill off second chess program */
9043         if (second.isr != NULL)
9044           RemoveInputSource(second.isr);
9045         second.isr = NULL;
9046     
9047         if (second.pr != NoProc) {
9048             DoSleep( appData.delayBeforeQuit );
9049             SendToProgram("quit\n", &second);
9050             DoSleep( appData.delayAfterQuit );
9051             DestroyChildProcess(second.pr, second.useSigterm);
9052         }
9053         second.pr = NoProc;
9054     }
9055
9056     if (matchMode && gameMode == TwoMachinesPlay) {
9057         switch (result) {
9058         case WhiteWins:
9059           if (first.twoMachinesColor[0] == 'w') {
9060             first.matchWins++;
9061           } else {
9062             second.matchWins++;
9063           }
9064           break;
9065         case BlackWins:
9066           if (first.twoMachinesColor[0] == 'b') {
9067             first.matchWins++;
9068           } else {
9069             second.matchWins++;
9070           }
9071           break;
9072         default:
9073           break;
9074         }
9075         if (matchGame < appData.matchGames) {
9076             char *tmp;
9077             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9078                 tmp = first.twoMachinesColor;
9079                 first.twoMachinesColor = second.twoMachinesColor;
9080                 second.twoMachinesColor = tmp;
9081             }
9082             gameMode = nextGameMode;
9083             matchGame++;
9084             if(appData.matchPause>10000 || appData.matchPause<10)
9085                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9086             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9087             endingGame = 0; /* [HGM] crash */
9088             return;
9089         } else {
9090             char buf[MSG_SIZ];
9091             gameMode = nextGameMode;
9092             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9093                     first.tidy, second.tidy,
9094                     first.matchWins, second.matchWins,
9095                     appData.matchGames - (first.matchWins + second.matchWins));
9096             DisplayFatalError(buf, 0, 0);
9097         }
9098     }
9099     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9100         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9101       ExitAnalyzeMode();
9102     gameMode = nextGameMode;
9103     ModeHighlight();
9104     endingGame = 0;  /* [HGM] crash */
9105 }
9106
9107 /* Assumes program was just initialized (initString sent).
9108    Leaves program in force mode. */
9109 void
9110 FeedMovesToProgram(cps, upto) 
9111      ChessProgramState *cps;
9112      int upto;
9113 {
9114     int i;
9115     
9116     if (appData.debugMode)
9117       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9118               startedFromSetupPosition ? "position and " : "",
9119               backwardMostMove, upto, cps->which);
9120     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9121         // [HGM] variantswitch: make engine aware of new variant
9122         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9123                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9124         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9125         SendToProgram(buf, cps);
9126         currentlyInitializedVariant = gameInfo.variant;
9127     }
9128     SendToProgram("force\n", cps);
9129     if (startedFromSetupPosition) {
9130         SendBoard(cps, backwardMostMove);
9131     if (appData.debugMode) {
9132         fprintf(debugFP, "feedMoves\n");
9133     }
9134     }
9135     for (i = backwardMostMove; i < upto; i++) {
9136         SendMoveToProgram(i, cps);
9137     }
9138 }
9139
9140
9141 void
9142 ResurrectChessProgram()
9143 {
9144      /* The chess program may have exited.
9145         If so, restart it and feed it all the moves made so far. */
9146
9147     if (appData.noChessProgram || first.pr != NoProc) return;
9148     
9149     StartChessProgram(&first);
9150     InitChessProgram(&first, FALSE);
9151     FeedMovesToProgram(&first, currentMove);
9152
9153     if (!first.sendTime) {
9154         /* can't tell gnuchess what its clock should read,
9155            so we bow to its notion. */
9156         ResetClocks();
9157         timeRemaining[0][currentMove] = whiteTimeRemaining;
9158         timeRemaining[1][currentMove] = blackTimeRemaining;
9159     }
9160
9161     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9162                 appData.icsEngineAnalyze) && first.analysisSupport) {
9163       SendToProgram("analyze\n", &first);
9164       first.analyzing = TRUE;
9165     }
9166 }
9167
9168 /*
9169  * Button procedures
9170  */
9171 void
9172 Reset(redraw, init)
9173      int redraw, init;
9174 {
9175     int i;
9176
9177     if (appData.debugMode) {
9178         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9179                 redraw, init, gameMode);
9180     }
9181     CleanupTail(); // [HGM] vari: delete any stored variations
9182     pausing = pauseExamInvalid = FALSE;
9183     startedFromSetupPosition = blackPlaysFirst = FALSE;
9184     firstMove = TRUE;
9185     whiteFlag = blackFlag = FALSE;
9186     userOfferedDraw = FALSE;
9187     hintRequested = bookRequested = FALSE;
9188     first.maybeThinking = FALSE;
9189     second.maybeThinking = FALSE;
9190     first.bookSuspend = FALSE; // [HGM] book
9191     second.bookSuspend = FALSE;
9192     thinkOutput[0] = NULLCHAR;
9193     lastHint[0] = NULLCHAR;
9194     ClearGameInfo(&gameInfo);
9195     gameInfo.variant = StringToVariant(appData.variant);
9196     ics_user_moved = ics_clock_paused = FALSE;
9197     ics_getting_history = H_FALSE;
9198     ics_gamenum = -1;
9199     white_holding[0] = black_holding[0] = NULLCHAR;
9200     ClearProgramStats();
9201     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9202     
9203     ResetFrontEnd();
9204     ClearHighlights();
9205     flipView = appData.flipView;
9206     ClearPremoveHighlights();
9207     gotPremove = FALSE;
9208     alarmSounded = FALSE;
9209
9210     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9211     if(appData.serverMovesName != NULL) {
9212         /* [HGM] prepare to make moves file for broadcasting */
9213         clock_t t = clock();
9214         if(serverMoves != NULL) fclose(serverMoves);
9215         serverMoves = fopen(appData.serverMovesName, "r");
9216         if(serverMoves != NULL) {
9217             fclose(serverMoves);
9218             /* delay 15 sec before overwriting, so all clients can see end */
9219             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9220         }
9221         serverMoves = fopen(appData.serverMovesName, "w");
9222     }
9223
9224     ExitAnalyzeMode();
9225     gameMode = BeginningOfGame;
9226     ModeHighlight();
9227     if(appData.icsActive) gameInfo.variant = VariantNormal;
9228     currentMove = forwardMostMove = backwardMostMove = 0;
9229     InitPosition(redraw);
9230     for (i = 0; i < MAX_MOVES; i++) {
9231         if (commentList[i] != NULL) {
9232             free(commentList[i]);
9233             commentList[i] = NULL;
9234         }
9235     }
9236     ResetClocks();
9237     timeRemaining[0][0] = whiteTimeRemaining;
9238     timeRemaining[1][0] = blackTimeRemaining;
9239     if (first.pr == NULL) {
9240         StartChessProgram(&first);
9241     }
9242     if (init) {
9243             InitChessProgram(&first, startedFromSetupPosition);
9244     }
9245     DisplayTitle("");
9246     DisplayMessage("", "");
9247     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9248     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9249 }
9250
9251 void
9252 AutoPlayGameLoop()
9253 {
9254     for (;;) {
9255         if (!AutoPlayOneMove())
9256           return;
9257         if (matchMode || appData.timeDelay == 0)
9258           continue;
9259         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9260           return;
9261         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9262         break;
9263     }
9264 }
9265
9266
9267 int
9268 AutoPlayOneMove()
9269 {
9270     int fromX, fromY, toX, toY;
9271
9272     if (appData.debugMode) {
9273       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9274     }
9275
9276     if (gameMode != PlayFromGameFile)
9277       return FALSE;
9278
9279     if (currentMove >= forwardMostMove) {
9280       gameMode = EditGame;
9281       ModeHighlight();
9282
9283       /* [AS] Clear current move marker at the end of a game */
9284       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9285
9286       return FALSE;
9287     }
9288     
9289     toX = moveList[currentMove][2] - AAA;
9290     toY = moveList[currentMove][3] - ONE;
9291
9292     if (moveList[currentMove][1] == '@') {
9293         if (appData.highlightLastMove) {
9294             SetHighlights(-1, -1, toX, toY);
9295         }
9296     } else {
9297         fromX = moveList[currentMove][0] - AAA;
9298         fromY = moveList[currentMove][1] - ONE;
9299
9300         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9301
9302         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9303
9304         if (appData.highlightLastMove) {
9305             SetHighlights(fromX, fromY, toX, toY);
9306         }
9307     }
9308     DisplayMove(currentMove);
9309     SendMoveToProgram(currentMove++, &first);
9310     DisplayBothClocks();
9311     DrawPosition(FALSE, boards[currentMove]);
9312     // [HGM] PV info: always display, routine tests if empty
9313     DisplayComment(currentMove - 1, commentList[currentMove]);
9314     return TRUE;
9315 }
9316
9317
9318 int
9319 LoadGameOneMove(readAhead)
9320      ChessMove readAhead;
9321 {
9322     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9323     char promoChar = NULLCHAR;
9324     ChessMove moveType;
9325     char move[MSG_SIZ];
9326     char *p, *q;
9327     
9328     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9329         gameMode != AnalyzeMode && gameMode != Training) {
9330         gameFileFP = NULL;
9331         return FALSE;
9332     }
9333     
9334     yyboardindex = forwardMostMove;
9335     if (readAhead != (ChessMove)0) {
9336       moveType = readAhead;
9337     } else {
9338       if (gameFileFP == NULL)
9339           return FALSE;
9340       moveType = (ChessMove) yylex();
9341     }
9342     
9343     done = FALSE;
9344     switch (moveType) {
9345       case Comment:
9346         if (appData.debugMode) 
9347           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9348         p = yy_text;
9349
9350         /* append the comment but don't display it */
9351         AppendComment(currentMove, p, FALSE);
9352         return TRUE;
9353
9354       case WhiteCapturesEnPassant:
9355       case BlackCapturesEnPassant:
9356       case WhitePromotionChancellor:
9357       case BlackPromotionChancellor:
9358       case WhitePromotionArchbishop:
9359       case BlackPromotionArchbishop:
9360       case WhitePromotionCentaur:
9361       case BlackPromotionCentaur:
9362       case WhitePromotionQueen:
9363       case BlackPromotionQueen:
9364       case WhitePromotionRook:
9365       case BlackPromotionRook:
9366       case WhitePromotionBishop:
9367       case BlackPromotionBishop:
9368       case WhitePromotionKnight:
9369       case BlackPromotionKnight:
9370       case WhitePromotionKing:
9371       case BlackPromotionKing:
9372       case NormalMove:
9373       case WhiteKingSideCastle:
9374       case WhiteQueenSideCastle:
9375       case BlackKingSideCastle:
9376       case BlackQueenSideCastle:
9377       case WhiteKingSideCastleWild:
9378       case WhiteQueenSideCastleWild:
9379       case BlackKingSideCastleWild:
9380       case BlackQueenSideCastleWild:
9381       /* PUSH Fabien */
9382       case WhiteHSideCastleFR:
9383       case WhiteASideCastleFR:
9384       case BlackHSideCastleFR:
9385       case BlackASideCastleFR:
9386       /* POP Fabien */
9387         if (appData.debugMode)
9388           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9389         fromX = currentMoveString[0] - AAA;
9390         fromY = currentMoveString[1] - ONE;
9391         toX = currentMoveString[2] - AAA;
9392         toY = currentMoveString[3] - ONE;
9393         promoChar = currentMoveString[4];
9394         break;
9395
9396       case WhiteDrop:
9397       case BlackDrop:
9398         if (appData.debugMode)
9399           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9400         fromX = moveType == WhiteDrop ?
9401           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9402         (int) CharToPiece(ToLower(currentMoveString[0]));
9403         fromY = DROP_RANK;
9404         toX = currentMoveString[2] - AAA;
9405         toY = currentMoveString[3] - ONE;
9406         break;
9407
9408       case WhiteWins:
9409       case BlackWins:
9410       case GameIsDrawn:
9411       case GameUnfinished:
9412         if (appData.debugMode)
9413           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9414         p = strchr(yy_text, '{');
9415         if (p == NULL) p = strchr(yy_text, '(');
9416         if (p == NULL) {
9417             p = yy_text;
9418             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9419         } else {
9420             q = strchr(p, *p == '{' ? '}' : ')');
9421             if (q != NULL) *q = NULLCHAR;
9422             p++;
9423         }
9424         GameEnds(moveType, p, GE_FILE);
9425         done = TRUE;
9426         if (cmailMsgLoaded) {
9427             ClearHighlights();
9428             flipView = WhiteOnMove(currentMove);
9429             if (moveType == GameUnfinished) flipView = !flipView;
9430             if (appData.debugMode)
9431               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9432         }
9433         break;
9434
9435       case (ChessMove) 0:       /* end of file */
9436         if (appData.debugMode)
9437           fprintf(debugFP, "Parser hit end of file\n");
9438         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9439           case MT_NONE:
9440           case MT_CHECK:
9441             break;
9442           case MT_CHECKMATE:
9443           case MT_STAINMATE:
9444             if (WhiteOnMove(currentMove)) {
9445                 GameEnds(BlackWins, "Black mates", GE_FILE);
9446             } else {
9447                 GameEnds(WhiteWins, "White mates", GE_FILE);
9448             }
9449             break;
9450           case MT_STALEMATE:
9451             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9452             break;
9453         }
9454         done = TRUE;
9455         break;
9456
9457       case MoveNumberOne:
9458         if (lastLoadGameStart == GNUChessGame) {
9459             /* GNUChessGames have numbers, but they aren't move numbers */
9460             if (appData.debugMode)
9461               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9462                       yy_text, (int) moveType);
9463             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9464         }
9465         /* else fall thru */
9466
9467       case XBoardGame:
9468       case GNUChessGame:
9469       case PGNTag:
9470         /* Reached start of next game in file */
9471         if (appData.debugMode)
9472           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9473         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9474           case MT_NONE:
9475           case MT_CHECK:
9476             break;
9477           case MT_CHECKMATE:
9478           case MT_STAINMATE:
9479             if (WhiteOnMove(currentMove)) {
9480                 GameEnds(BlackWins, "Black mates", GE_FILE);
9481             } else {
9482                 GameEnds(WhiteWins, "White mates", GE_FILE);
9483             }
9484             break;
9485           case MT_STALEMATE:
9486             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9487             break;
9488         }
9489         done = TRUE;
9490         break;
9491
9492       case PositionDiagram:     /* should not happen; ignore */
9493       case ElapsedTime:         /* ignore */
9494       case NAG:                 /* ignore */
9495         if (appData.debugMode)
9496           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9497                   yy_text, (int) moveType);
9498         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9499
9500       case IllegalMove:
9501         if (appData.testLegality) {
9502             if (appData.debugMode)
9503               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9504             sprintf(move, _("Illegal move: %d.%s%s"),
9505                     (forwardMostMove / 2) + 1,
9506                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9507             DisplayError(move, 0);
9508             done = TRUE;
9509         } else {
9510             if (appData.debugMode)
9511               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9512                       yy_text, currentMoveString);
9513             fromX = currentMoveString[0] - AAA;
9514             fromY = currentMoveString[1] - ONE;
9515             toX = currentMoveString[2] - AAA;
9516             toY = currentMoveString[3] - ONE;
9517             promoChar = currentMoveString[4];
9518         }
9519         break;
9520
9521       case AmbiguousMove:
9522         if (appData.debugMode)
9523           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9524         sprintf(move, _("Ambiguous move: %d.%s%s"),
9525                 (forwardMostMove / 2) + 1,
9526                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9527         DisplayError(move, 0);
9528         done = TRUE;
9529         break;
9530
9531       default:
9532       case ImpossibleMove:
9533         if (appData.debugMode)
9534           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9535         sprintf(move, _("Illegal move: %d.%s%s"),
9536                 (forwardMostMove / 2) + 1,
9537                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9538         DisplayError(move, 0);
9539         done = TRUE;
9540         break;
9541     }
9542
9543     if (done) {
9544         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9545             DrawPosition(FALSE, boards[currentMove]);
9546             DisplayBothClocks();
9547             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9548               DisplayComment(currentMove - 1, commentList[currentMove]);
9549         }
9550         (void) StopLoadGameTimer();
9551         gameFileFP = NULL;
9552         cmailOldMove = forwardMostMove;
9553         return FALSE;
9554     } else {
9555         /* currentMoveString is set as a side-effect of yylex */
9556         strcat(currentMoveString, "\n");
9557         strcpy(moveList[forwardMostMove], currentMoveString);
9558         
9559         thinkOutput[0] = NULLCHAR;
9560         MakeMove(fromX, fromY, toX, toY, promoChar);
9561         currentMove = forwardMostMove;
9562         return TRUE;
9563     }
9564 }
9565
9566 /* Load the nth game from the given file */
9567 int
9568 LoadGameFromFile(filename, n, title, useList)
9569      char *filename;
9570      int n;
9571      char *title;
9572      /*Boolean*/ int useList;
9573 {
9574     FILE *f;
9575     char buf[MSG_SIZ];
9576
9577     if (strcmp(filename, "-") == 0) {
9578         f = stdin;
9579         title = "stdin";
9580     } else {
9581         f = fopen(filename, "rb");
9582         if (f == NULL) {
9583           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9584             DisplayError(buf, errno);
9585             return FALSE;
9586         }
9587     }
9588     if (fseek(f, 0, 0) == -1) {
9589         /* f is not seekable; probably a pipe */
9590         useList = FALSE;
9591     }
9592     if (useList && n == 0) {
9593         int error = GameListBuild(f);
9594         if (error) {
9595             DisplayError(_("Cannot build game list"), error);
9596         } else if (!ListEmpty(&gameList) &&
9597                    ((ListGame *) gameList.tailPred)->number > 1) {
9598             GameListPopUp(f, title);
9599             return TRUE;
9600         }
9601         GameListDestroy();
9602         n = 1;
9603     }
9604     if (n == 0) n = 1;
9605     return LoadGame(f, n, title, FALSE);
9606 }
9607
9608
9609 void
9610 MakeRegisteredMove()
9611 {
9612     int fromX, fromY, toX, toY;
9613     char promoChar;
9614     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9615         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9616           case CMAIL_MOVE:
9617           case CMAIL_DRAW:
9618             if (appData.debugMode)
9619               fprintf(debugFP, "Restoring %s for game %d\n",
9620                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9621     
9622             thinkOutput[0] = NULLCHAR;
9623             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9624             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9625             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9626             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9627             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9628             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9629             MakeMove(fromX, fromY, toX, toY, promoChar);
9630             ShowMove(fromX, fromY, toX, toY);
9631               
9632             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9633               case MT_NONE:
9634               case MT_CHECK:
9635                 break;
9636                 
9637               case MT_CHECKMATE:
9638               case MT_STAINMATE:
9639                 if (WhiteOnMove(currentMove)) {
9640                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9641                 } else {
9642                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9643                 }
9644                 break;
9645                 
9646               case MT_STALEMATE:
9647                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9648                 break;
9649             }
9650
9651             break;
9652             
9653           case CMAIL_RESIGN:
9654             if (WhiteOnMove(currentMove)) {
9655                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9656             } else {
9657                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9658             }
9659             break;
9660             
9661           case CMAIL_ACCEPT:
9662             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9663             break;
9664               
9665           default:
9666             break;
9667         }
9668     }
9669
9670     return;
9671 }
9672
9673 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9674 int
9675 CmailLoadGame(f, gameNumber, title, useList)
9676      FILE *f;
9677      int gameNumber;
9678      char *title;
9679      int useList;
9680 {
9681     int retVal;
9682
9683     if (gameNumber > nCmailGames) {
9684         DisplayError(_("No more games in this message"), 0);
9685         return FALSE;
9686     }
9687     if (f == lastLoadGameFP) {
9688         int offset = gameNumber - lastLoadGameNumber;
9689         if (offset == 0) {
9690             cmailMsg[0] = NULLCHAR;
9691             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9692                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9693                 nCmailMovesRegistered--;
9694             }
9695             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9696             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9697                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9698             }
9699         } else {
9700             if (! RegisterMove()) return FALSE;
9701         }
9702     }
9703
9704     retVal = LoadGame(f, gameNumber, title, useList);
9705
9706     /* Make move registered during previous look at this game, if any */
9707     MakeRegisteredMove();
9708
9709     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9710         commentList[currentMove]
9711           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9712         DisplayComment(currentMove - 1, commentList[currentMove]);
9713     }
9714
9715     return retVal;
9716 }
9717
9718 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9719 int
9720 ReloadGame(offset)
9721      int offset;
9722 {
9723     int gameNumber = lastLoadGameNumber + offset;
9724     if (lastLoadGameFP == NULL) {
9725         DisplayError(_("No game has been loaded yet"), 0);
9726         return FALSE;
9727     }
9728     if (gameNumber <= 0) {
9729         DisplayError(_("Can't back up any further"), 0);
9730         return FALSE;
9731     }
9732     if (cmailMsgLoaded) {
9733         return CmailLoadGame(lastLoadGameFP, gameNumber,
9734                              lastLoadGameTitle, lastLoadGameUseList);
9735     } else {
9736         return LoadGame(lastLoadGameFP, gameNumber,
9737                         lastLoadGameTitle, lastLoadGameUseList);
9738     }
9739 }
9740
9741
9742
9743 /* Load the nth game from open file f */
9744 int
9745 LoadGame(f, gameNumber, title, useList)
9746      FILE *f;
9747      int gameNumber;
9748      char *title;
9749      int useList;
9750 {
9751     ChessMove cm;
9752     char buf[MSG_SIZ];
9753     int gn = gameNumber;
9754     ListGame *lg = NULL;
9755     int numPGNTags = 0;
9756     int err;
9757     GameMode oldGameMode;
9758     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9759
9760     if (appData.debugMode) 
9761         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9762
9763     if (gameMode == Training )
9764         SetTrainingModeOff();
9765
9766     oldGameMode = gameMode;
9767     if (gameMode != BeginningOfGame) {
9768       Reset(FALSE, TRUE);
9769     }
9770
9771     gameFileFP = f;
9772     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9773         fclose(lastLoadGameFP);
9774     }
9775
9776     if (useList) {
9777         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9778         
9779         if (lg) {
9780             fseek(f, lg->offset, 0);
9781             GameListHighlight(gameNumber);
9782             gn = 1;
9783         }
9784         else {
9785             DisplayError(_("Game number out of range"), 0);
9786             return FALSE;
9787         }
9788     } else {
9789         GameListDestroy();
9790         if (fseek(f, 0, 0) == -1) {
9791             if (f == lastLoadGameFP ?
9792                 gameNumber == lastLoadGameNumber + 1 :
9793                 gameNumber == 1) {
9794                 gn = 1;
9795             } else {
9796                 DisplayError(_("Can't seek on game file"), 0);
9797                 return FALSE;
9798             }
9799         }
9800     }
9801     lastLoadGameFP = f;
9802     lastLoadGameNumber = gameNumber;
9803     strcpy(lastLoadGameTitle, title);
9804     lastLoadGameUseList = useList;
9805
9806     yynewfile(f);
9807
9808     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9809       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9810                 lg->gameInfo.black);
9811             DisplayTitle(buf);
9812     } else if (*title != NULLCHAR) {
9813         if (gameNumber > 1) {
9814             sprintf(buf, "%s %d", title, gameNumber);
9815             DisplayTitle(buf);
9816         } else {
9817             DisplayTitle(title);
9818         }
9819     }
9820
9821     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9822         gameMode = PlayFromGameFile;
9823         ModeHighlight();
9824     }
9825
9826     currentMove = forwardMostMove = backwardMostMove = 0;
9827     CopyBoard(boards[0], initialPosition);
9828     StopClocks();
9829
9830     /*
9831      * Skip the first gn-1 games in the file.
9832      * Also skip over anything that precedes an identifiable 
9833      * start of game marker, to avoid being confused by 
9834      * garbage at the start of the file.  Currently 
9835      * recognized start of game markers are the move number "1",
9836      * the pattern "gnuchess .* game", the pattern
9837      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9838      * A game that starts with one of the latter two patterns
9839      * will also have a move number 1, possibly
9840      * following a position diagram.
9841      * 5-4-02: Let's try being more lenient and allowing a game to
9842      * start with an unnumbered move.  Does that break anything?
9843      */
9844     cm = lastLoadGameStart = (ChessMove) 0;
9845     while (gn > 0) {
9846         yyboardindex = forwardMostMove;
9847         cm = (ChessMove) yylex();
9848         switch (cm) {
9849           case (ChessMove) 0:
9850             if (cmailMsgLoaded) {
9851                 nCmailGames = CMAIL_MAX_GAMES - gn;
9852             } else {
9853                 Reset(TRUE, TRUE);
9854                 DisplayError(_("Game not found in file"), 0);
9855             }
9856             return FALSE;
9857
9858           case GNUChessGame:
9859           case XBoardGame:
9860             gn--;
9861             lastLoadGameStart = cm;
9862             break;
9863             
9864           case MoveNumberOne:
9865             switch (lastLoadGameStart) {
9866               case GNUChessGame:
9867               case XBoardGame:
9868               case PGNTag:
9869                 break;
9870               case MoveNumberOne:
9871               case (ChessMove) 0:
9872                 gn--;           /* count this game */
9873                 lastLoadGameStart = cm;
9874                 break;
9875               default:
9876                 /* impossible */
9877                 break;
9878             }
9879             break;
9880
9881           case PGNTag:
9882             switch (lastLoadGameStart) {
9883               case GNUChessGame:
9884               case PGNTag:
9885               case MoveNumberOne:
9886               case (ChessMove) 0:
9887                 gn--;           /* count this game */
9888                 lastLoadGameStart = cm;
9889                 break;
9890               case XBoardGame:
9891                 lastLoadGameStart = cm; /* game counted already */
9892                 break;
9893               default:
9894                 /* impossible */
9895                 break;
9896             }
9897             if (gn > 0) {
9898                 do {
9899                     yyboardindex = forwardMostMove;
9900                     cm = (ChessMove) yylex();
9901                 } while (cm == PGNTag || cm == Comment);
9902             }
9903             break;
9904
9905           case WhiteWins:
9906           case BlackWins:
9907           case GameIsDrawn:
9908             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9909                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
9910                     != CMAIL_OLD_RESULT) {
9911                     nCmailResults ++ ;
9912                     cmailResult[  CMAIL_MAX_GAMES
9913                                 - gn - 1] = CMAIL_OLD_RESULT;
9914                 }
9915             }
9916             break;
9917
9918           case NormalMove:
9919             /* Only a NormalMove can be at the start of a game
9920              * without a position diagram. */
9921             if (lastLoadGameStart == (ChessMove) 0) {
9922               gn--;
9923               lastLoadGameStart = MoveNumberOne;
9924             }
9925             break;
9926
9927           default:
9928             break;
9929         }
9930     }
9931     
9932     if (appData.debugMode)
9933       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9934
9935     if (cm == XBoardGame) {
9936         /* Skip any header junk before position diagram and/or move 1 */
9937         for (;;) {
9938             yyboardindex = forwardMostMove;
9939             cm = (ChessMove) yylex();
9940
9941             if (cm == (ChessMove) 0 ||
9942                 cm == GNUChessGame || cm == XBoardGame) {
9943                 /* Empty game; pretend end-of-file and handle later */
9944                 cm = (ChessMove) 0;
9945                 break;
9946             }
9947
9948             if (cm == MoveNumberOne || cm == PositionDiagram ||
9949                 cm == PGNTag || cm == Comment)
9950               break;
9951         }
9952     } else if (cm == GNUChessGame) {
9953         if (gameInfo.event != NULL) {
9954             free(gameInfo.event);
9955         }
9956         gameInfo.event = StrSave(yy_text);
9957     }   
9958
9959     startedFromSetupPosition = FALSE;
9960     while (cm == PGNTag) {
9961         if (appData.debugMode) 
9962           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9963         err = ParsePGNTag(yy_text, &gameInfo);
9964         if (!err) numPGNTags++;
9965
9966         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9967         if(gameInfo.variant != oldVariant) {
9968             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9969             InitPosition(TRUE);
9970             oldVariant = gameInfo.variant;
9971             if (appData.debugMode) 
9972               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9973         }
9974
9975
9976         if (gameInfo.fen != NULL) {
9977           Board initial_position;
9978           startedFromSetupPosition = TRUE;
9979           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9980             Reset(TRUE, TRUE);
9981             DisplayError(_("Bad FEN position in file"), 0);
9982             return FALSE;
9983           }
9984           CopyBoard(boards[0], initial_position);
9985           if (blackPlaysFirst) {
9986             currentMove = forwardMostMove = backwardMostMove = 1;
9987             CopyBoard(boards[1], initial_position);
9988             strcpy(moveList[0], "");
9989             strcpy(parseList[0], "");
9990             timeRemaining[0][1] = whiteTimeRemaining;
9991             timeRemaining[1][1] = blackTimeRemaining;
9992             if (commentList[0] != NULL) {
9993               commentList[1] = commentList[0];
9994               commentList[0] = NULL;
9995             }
9996           } else {
9997             currentMove = forwardMostMove = backwardMostMove = 0;
9998           }
9999           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10000           {   int i;
10001               initialRulePlies = FENrulePlies;
10002               for( i=0; i< nrCastlingRights; i++ )
10003                   initialRights[i] = initial_position[CASTLING][i];
10004           }
10005           yyboardindex = forwardMostMove;
10006           free(gameInfo.fen);
10007           gameInfo.fen = NULL;
10008         }
10009
10010         yyboardindex = forwardMostMove;
10011         cm = (ChessMove) yylex();
10012
10013         /* Handle comments interspersed among the tags */
10014         while (cm == Comment) {
10015             char *p;
10016             if (appData.debugMode) 
10017               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10018             p = yy_text;
10019             AppendComment(currentMove, p, FALSE);
10020             yyboardindex = forwardMostMove;
10021             cm = (ChessMove) yylex();
10022         }
10023     }
10024
10025     /* don't rely on existence of Event tag since if game was
10026      * pasted from clipboard the Event tag may not exist
10027      */
10028     if (numPGNTags > 0){
10029         char *tags;
10030         if (gameInfo.variant == VariantNormal) {
10031           gameInfo.variant = StringToVariant(gameInfo.event);
10032         }
10033         if (!matchMode) {
10034           if( appData.autoDisplayTags ) {
10035             tags = PGNTags(&gameInfo);
10036             TagsPopUp(tags, CmailMsg());
10037             free(tags);
10038           }
10039         }
10040     } else {
10041         /* Make something up, but don't display it now */
10042         SetGameInfo();
10043         TagsPopDown();
10044     }
10045
10046     if (cm == PositionDiagram) {
10047         int i, j;
10048         char *p;
10049         Board initial_position;
10050
10051         if (appData.debugMode)
10052           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10053
10054         if (!startedFromSetupPosition) {
10055             p = yy_text;
10056             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10057               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10058                 switch (*p) {
10059                   case '[':
10060                   case '-':
10061                   case ' ':
10062                   case '\t':
10063                   case '\n':
10064                   case '\r':
10065                     break;
10066                   default:
10067                     initial_position[i][j++] = CharToPiece(*p);
10068                     break;
10069                 }
10070             while (*p == ' ' || *p == '\t' ||
10071                    *p == '\n' || *p == '\r') p++;
10072         
10073             if (strncmp(p, "black", strlen("black"))==0)
10074               blackPlaysFirst = TRUE;
10075             else
10076               blackPlaysFirst = FALSE;
10077             startedFromSetupPosition = TRUE;
10078         
10079             CopyBoard(boards[0], initial_position);
10080             if (blackPlaysFirst) {
10081                 currentMove = forwardMostMove = backwardMostMove = 1;
10082                 CopyBoard(boards[1], initial_position);
10083                 strcpy(moveList[0], "");
10084                 strcpy(parseList[0], "");
10085                 timeRemaining[0][1] = whiteTimeRemaining;
10086                 timeRemaining[1][1] = blackTimeRemaining;
10087                 if (commentList[0] != NULL) {
10088                     commentList[1] = commentList[0];
10089                     commentList[0] = NULL;
10090                 }
10091             } else {
10092                 currentMove = forwardMostMove = backwardMostMove = 0;
10093             }
10094         }
10095         yyboardindex = forwardMostMove;
10096         cm = (ChessMove) yylex();
10097     }
10098
10099     if (first.pr == NoProc) {
10100         StartChessProgram(&first);
10101     }
10102     InitChessProgram(&first, FALSE);
10103     SendToProgram("force\n", &first);
10104     if (startedFromSetupPosition) {
10105         SendBoard(&first, forwardMostMove);
10106     if (appData.debugMode) {
10107         fprintf(debugFP, "Load Game\n");
10108     }
10109         DisplayBothClocks();
10110     }      
10111
10112     /* [HGM] server: flag to write setup moves in broadcast file as one */
10113     loadFlag = appData.suppressLoadMoves;
10114
10115     while (cm == Comment) {
10116         char *p;
10117         if (appData.debugMode) 
10118           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10119         p = yy_text;
10120         AppendComment(currentMove, p, FALSE);
10121         yyboardindex = forwardMostMove;
10122         cm = (ChessMove) yylex();
10123     }
10124
10125     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10126         cm == WhiteWins || cm == BlackWins ||
10127         cm == GameIsDrawn || cm == GameUnfinished) {
10128         DisplayMessage("", _("No moves in game"));
10129         if (cmailMsgLoaded) {
10130             if (appData.debugMode)
10131               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10132             ClearHighlights();
10133             flipView = FALSE;
10134         }
10135         DrawPosition(FALSE, boards[currentMove]);
10136         DisplayBothClocks();
10137         gameMode = EditGame;
10138         ModeHighlight();
10139         gameFileFP = NULL;
10140         cmailOldMove = 0;
10141         return TRUE;
10142     }
10143
10144     // [HGM] PV info: routine tests if comment empty
10145     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10146         DisplayComment(currentMove - 1, commentList[currentMove]);
10147     }
10148     if (!matchMode && appData.timeDelay != 0) 
10149       DrawPosition(FALSE, boards[currentMove]);
10150
10151     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10152       programStats.ok_to_send = 1;
10153     }
10154
10155     /* if the first token after the PGN tags is a move
10156      * and not move number 1, retrieve it from the parser 
10157      */
10158     if (cm != MoveNumberOne)
10159         LoadGameOneMove(cm);
10160
10161     /* load the remaining moves from the file */
10162     while (LoadGameOneMove((ChessMove)0)) {
10163       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10164       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10165     }
10166
10167     /* rewind to the start of the game */
10168     currentMove = backwardMostMove;
10169
10170     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10171
10172     if (oldGameMode == AnalyzeFile ||
10173         oldGameMode == AnalyzeMode) {
10174       AnalyzeFileEvent();
10175     }
10176
10177     if (matchMode || appData.timeDelay == 0) {
10178       ToEndEvent();
10179       gameMode = EditGame;
10180       ModeHighlight();
10181     } else if (appData.timeDelay > 0) {
10182       AutoPlayGameLoop();
10183     }
10184
10185     if (appData.debugMode) 
10186         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10187
10188     loadFlag = 0; /* [HGM] true game starts */
10189     return TRUE;
10190 }
10191
10192 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10193 int
10194 ReloadPosition(offset)
10195      int offset;
10196 {
10197     int positionNumber = lastLoadPositionNumber + offset;
10198     if (lastLoadPositionFP == NULL) {
10199         DisplayError(_("No position has been loaded yet"), 0);
10200         return FALSE;
10201     }
10202     if (positionNumber <= 0) {
10203         DisplayError(_("Can't back up any further"), 0);
10204         return FALSE;
10205     }
10206     return LoadPosition(lastLoadPositionFP, positionNumber,
10207                         lastLoadPositionTitle);
10208 }
10209
10210 /* Load the nth position from the given file */
10211 int
10212 LoadPositionFromFile(filename, n, title)
10213      char *filename;
10214      int n;
10215      char *title;
10216 {
10217     FILE *f;
10218     char buf[MSG_SIZ];
10219
10220     if (strcmp(filename, "-") == 0) {
10221         return LoadPosition(stdin, n, "stdin");
10222     } else {
10223         f = fopen(filename, "rb");
10224         if (f == NULL) {
10225             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10226             DisplayError(buf, errno);
10227             return FALSE;
10228         } else {
10229             return LoadPosition(f, n, title);
10230         }
10231     }
10232 }
10233
10234 /* Load the nth position from the given open file, and close it */
10235 int
10236 LoadPosition(f, positionNumber, title)
10237      FILE *f;
10238      int positionNumber;
10239      char *title;
10240 {
10241     char *p, line[MSG_SIZ];
10242     Board initial_position;
10243     int i, j, fenMode, pn;
10244     
10245     if (gameMode == Training )
10246         SetTrainingModeOff();
10247
10248     if (gameMode != BeginningOfGame) {
10249         Reset(FALSE, TRUE);
10250     }
10251     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10252         fclose(lastLoadPositionFP);
10253     }
10254     if (positionNumber == 0) positionNumber = 1;
10255     lastLoadPositionFP = f;
10256     lastLoadPositionNumber = positionNumber;
10257     strcpy(lastLoadPositionTitle, title);
10258     if (first.pr == NoProc) {
10259       StartChessProgram(&first);
10260       InitChessProgram(&first, FALSE);
10261     }    
10262     pn = positionNumber;
10263     if (positionNumber < 0) {
10264         /* Negative position number means to seek to that byte offset */
10265         if (fseek(f, -positionNumber, 0) == -1) {
10266             DisplayError(_("Can't seek on position file"), 0);
10267             return FALSE;
10268         };
10269         pn = 1;
10270     } else {
10271         if (fseek(f, 0, 0) == -1) {
10272             if (f == lastLoadPositionFP ?
10273                 positionNumber == lastLoadPositionNumber + 1 :
10274                 positionNumber == 1) {
10275                 pn = 1;
10276             } else {
10277                 DisplayError(_("Can't seek on position file"), 0);
10278                 return FALSE;
10279             }
10280         }
10281     }
10282     /* See if this file is FEN or old-style xboard */
10283     if (fgets(line, MSG_SIZ, f) == NULL) {
10284         DisplayError(_("Position not found in file"), 0);
10285         return FALSE;
10286     }
10287     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10288     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10289
10290     if (pn >= 2) {
10291         if (fenMode || line[0] == '#') pn--;
10292         while (pn > 0) {
10293             /* skip positions before number pn */
10294             if (fgets(line, MSG_SIZ, f) == NULL) {
10295                 Reset(TRUE, TRUE);
10296                 DisplayError(_("Position not found in file"), 0);
10297                 return FALSE;
10298             }
10299             if (fenMode || line[0] == '#') pn--;
10300         }
10301     }
10302
10303     if (fenMode) {
10304         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10305             DisplayError(_("Bad FEN position in file"), 0);
10306             return FALSE;
10307         }
10308     } else {
10309         (void) fgets(line, MSG_SIZ, f);
10310         (void) fgets(line, MSG_SIZ, f);
10311     
10312         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10313             (void) fgets(line, MSG_SIZ, f);
10314             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10315                 if (*p == ' ')
10316                   continue;
10317                 initial_position[i][j++] = CharToPiece(*p);
10318             }
10319         }
10320     
10321         blackPlaysFirst = FALSE;
10322         if (!feof(f)) {
10323             (void) fgets(line, MSG_SIZ, f);
10324             if (strncmp(line, "black", strlen("black"))==0)
10325               blackPlaysFirst = TRUE;
10326         }
10327     }
10328     startedFromSetupPosition = TRUE;
10329     
10330     SendToProgram("force\n", &first);
10331     CopyBoard(boards[0], initial_position);
10332     if (blackPlaysFirst) {
10333         currentMove = forwardMostMove = backwardMostMove = 1;
10334         strcpy(moveList[0], "");
10335         strcpy(parseList[0], "");
10336         CopyBoard(boards[1], initial_position);
10337         DisplayMessage("", _("Black to play"));
10338     } else {
10339         currentMove = forwardMostMove = backwardMostMove = 0;
10340         DisplayMessage("", _("White to play"));
10341     }
10342     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10343     SendBoard(&first, forwardMostMove);
10344     if (appData.debugMode) {
10345 int i, j;
10346   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10347   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10348         fprintf(debugFP, "Load Position\n");
10349     }
10350
10351     if (positionNumber > 1) {
10352         sprintf(line, "%s %d", title, positionNumber);
10353         DisplayTitle(line);
10354     } else {
10355         DisplayTitle(title);
10356     }
10357     gameMode = EditGame;
10358     ModeHighlight();
10359     ResetClocks();
10360     timeRemaining[0][1] = whiteTimeRemaining;
10361     timeRemaining[1][1] = blackTimeRemaining;
10362     DrawPosition(FALSE, boards[currentMove]);
10363    
10364     return TRUE;
10365 }
10366
10367
10368 void
10369 CopyPlayerNameIntoFileName(dest, src)
10370      char **dest, *src;
10371 {
10372     while (*src != NULLCHAR && *src != ',') {
10373         if (*src == ' ') {
10374             *(*dest)++ = '_';
10375             src++;
10376         } else {
10377             *(*dest)++ = *src++;
10378         }
10379     }
10380 }
10381
10382 char *DefaultFileName(ext)
10383      char *ext;
10384 {
10385     static char def[MSG_SIZ];
10386     char *p;
10387
10388     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10389         p = def;
10390         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10391         *p++ = '-';
10392         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10393         *p++ = '.';
10394         strcpy(p, ext);
10395     } else {
10396         def[0] = NULLCHAR;
10397     }
10398     return def;
10399 }
10400
10401 /* Save the current game to the given file */
10402 int
10403 SaveGameToFile(filename, append)
10404      char *filename;
10405      int append;
10406 {
10407     FILE *f;
10408     char buf[MSG_SIZ];
10409
10410     if (strcmp(filename, "-") == 0) {
10411         return SaveGame(stdout, 0, NULL);
10412     } else {
10413         f = fopen(filename, append ? "a" : "w");
10414         if (f == NULL) {
10415             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10416             DisplayError(buf, errno);
10417             return FALSE;
10418         } else {
10419             return SaveGame(f, 0, NULL);
10420         }
10421     }
10422 }
10423
10424 char *
10425 SavePart(str)
10426      char *str;
10427 {
10428     static char buf[MSG_SIZ];
10429     char *p;
10430     
10431     p = strchr(str, ' ');
10432     if (p == NULL) return str;
10433     strncpy(buf, str, p - str);
10434     buf[p - str] = NULLCHAR;
10435     return buf;
10436 }
10437
10438 #define PGN_MAX_LINE 75
10439
10440 #define PGN_SIDE_WHITE  0
10441 #define PGN_SIDE_BLACK  1
10442
10443 /* [AS] */
10444 static int FindFirstMoveOutOfBook( int side )
10445 {
10446     int result = -1;
10447
10448     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10449         int index = backwardMostMove;
10450         int has_book_hit = 0;
10451
10452         if( (index % 2) != side ) {
10453             index++;
10454         }
10455
10456         while( index < forwardMostMove ) {
10457             /* Check to see if engine is in book */
10458             int depth = pvInfoList[index].depth;
10459             int score = pvInfoList[index].score;
10460             int in_book = 0;
10461
10462             if( depth <= 2 ) {
10463                 in_book = 1;
10464             }
10465             else if( score == 0 && depth == 63 ) {
10466                 in_book = 1; /* Zappa */
10467             }
10468             else if( score == 2 && depth == 99 ) {
10469                 in_book = 1; /* Abrok */
10470             }
10471
10472             has_book_hit += in_book;
10473
10474             if( ! in_book ) {
10475                 result = index;
10476
10477                 break;
10478             }
10479
10480             index += 2;
10481         }
10482     }
10483
10484     return result;
10485 }
10486
10487 /* [AS] */
10488 void GetOutOfBookInfo( char * buf )
10489 {
10490     int oob[2];
10491     int i;
10492     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10493
10494     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10495     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10496
10497     *buf = '\0';
10498
10499     if( oob[0] >= 0 || oob[1] >= 0 ) {
10500         for( i=0; i<2; i++ ) {
10501             int idx = oob[i];
10502
10503             if( idx >= 0 ) {
10504                 if( i > 0 && oob[0] >= 0 ) {
10505                     strcat( buf, "   " );
10506                 }
10507
10508                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10509                 sprintf( buf+strlen(buf), "%s%.2f", 
10510                     pvInfoList[idx].score >= 0 ? "+" : "",
10511                     pvInfoList[idx].score / 100.0 );
10512             }
10513         }
10514     }
10515 }
10516
10517 /* Save game in PGN style and close the file */
10518 int
10519 SaveGamePGN(f)
10520      FILE *f;
10521 {
10522     int i, offset, linelen, newblock;
10523     time_t tm;
10524 //    char *movetext;
10525     char numtext[32];
10526     int movelen, numlen, blank;
10527     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10528
10529     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10530     
10531     tm = time((time_t *) NULL);
10532     
10533     PrintPGNTags(f, &gameInfo);
10534     
10535     if (backwardMostMove > 0 || startedFromSetupPosition) {
10536         char *fen = PositionToFEN(backwardMostMove, NULL);
10537         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10538         fprintf(f, "\n{--------------\n");
10539         PrintPosition(f, backwardMostMove);
10540         fprintf(f, "--------------}\n");
10541         free(fen);
10542     }
10543     else {
10544         /* [AS] Out of book annotation */
10545         if( appData.saveOutOfBookInfo ) {
10546             char buf[64];
10547
10548             GetOutOfBookInfo( buf );
10549
10550             if( buf[0] != '\0' ) {
10551                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10552             }
10553         }
10554
10555         fprintf(f, "\n");
10556     }
10557
10558     i = backwardMostMove;
10559     linelen = 0;
10560     newblock = TRUE;
10561
10562     while (i < forwardMostMove) {
10563         /* Print comments preceding this move */
10564         if (commentList[i] != NULL) {
10565             if (linelen > 0) fprintf(f, "\n");
10566             fprintf(f, "%s", commentList[i]);
10567             linelen = 0;
10568             newblock = TRUE;
10569         }
10570
10571         /* Format move number */
10572         if ((i % 2) == 0) {
10573             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10574         } else {
10575             if (newblock) {
10576                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10577             } else {
10578                 numtext[0] = NULLCHAR;
10579             }
10580         }
10581         numlen = strlen(numtext);
10582         newblock = FALSE;
10583
10584         /* Print move number */
10585         blank = linelen > 0 && numlen > 0;
10586         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10587             fprintf(f, "\n");
10588             linelen = 0;
10589             blank = 0;
10590         }
10591         if (blank) {
10592             fprintf(f, " ");
10593             linelen++;
10594         }
10595         fprintf(f, "%s", numtext);
10596         linelen += numlen;
10597
10598         /* Get move */
10599         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10600         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10601
10602         /* Print move */
10603         blank = linelen > 0 && movelen > 0;
10604         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10605             fprintf(f, "\n");
10606             linelen = 0;
10607             blank = 0;
10608         }
10609         if (blank) {
10610             fprintf(f, " ");
10611             linelen++;
10612         }
10613         fprintf(f, "%s", move_buffer);
10614         linelen += movelen;
10615
10616         /* [AS] Add PV info if present */
10617         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10618             /* [HGM] add time */
10619             char buf[MSG_SIZ]; int seconds;
10620
10621             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10622
10623             if( seconds <= 0) buf[0] = 0; else
10624             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10625                 seconds = (seconds + 4)/10; // round to full seconds
10626                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10627                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10628             }
10629
10630             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10631                 pvInfoList[i].score >= 0 ? "+" : "",
10632                 pvInfoList[i].score / 100.0,
10633                 pvInfoList[i].depth,
10634                 buf );
10635
10636             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10637
10638             /* Print score/depth */
10639             blank = linelen > 0 && movelen > 0;
10640             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10641                 fprintf(f, "\n");
10642                 linelen = 0;
10643                 blank = 0;
10644             }
10645             if (blank) {
10646                 fprintf(f, " ");
10647                 linelen++;
10648             }
10649             fprintf(f, "%s", move_buffer);
10650             linelen += movelen;
10651         }
10652
10653         i++;
10654     }
10655     
10656     /* Start a new line */
10657     if (linelen > 0) fprintf(f, "\n");
10658
10659     /* Print comments after last move */
10660     if (commentList[i] != NULL) {
10661         fprintf(f, "%s\n", commentList[i]);
10662     }
10663
10664     /* Print result */
10665     if (gameInfo.resultDetails != NULL &&
10666         gameInfo.resultDetails[0] != NULLCHAR) {
10667         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10668                 PGNResult(gameInfo.result));
10669     } else {
10670         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10671     }
10672
10673     fclose(f);
10674     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10675     return TRUE;
10676 }
10677
10678 /* Save game in old style and close the file */
10679 int
10680 SaveGameOldStyle(f)
10681      FILE *f;
10682 {
10683     int i, offset;
10684     time_t tm;
10685     
10686     tm = time((time_t *) NULL);
10687     
10688     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10689     PrintOpponents(f);
10690     
10691     if (backwardMostMove > 0 || startedFromSetupPosition) {
10692         fprintf(f, "\n[--------------\n");
10693         PrintPosition(f, backwardMostMove);
10694         fprintf(f, "--------------]\n");
10695     } else {
10696         fprintf(f, "\n");
10697     }
10698
10699     i = backwardMostMove;
10700     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10701
10702     while (i < forwardMostMove) {
10703         if (commentList[i] != NULL) {
10704             fprintf(f, "[%s]\n", commentList[i]);
10705         }
10706
10707         if ((i % 2) == 1) {
10708             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10709             i++;
10710         } else {
10711             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10712             i++;
10713             if (commentList[i] != NULL) {
10714                 fprintf(f, "\n");
10715                 continue;
10716             }
10717             if (i >= forwardMostMove) {
10718                 fprintf(f, "\n");
10719                 break;
10720             }
10721             fprintf(f, "%s\n", parseList[i]);
10722             i++;
10723         }
10724     }
10725     
10726     if (commentList[i] != NULL) {
10727         fprintf(f, "[%s]\n", commentList[i]);
10728     }
10729
10730     /* This isn't really the old style, but it's close enough */
10731     if (gameInfo.resultDetails != NULL &&
10732         gameInfo.resultDetails[0] != NULLCHAR) {
10733         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10734                 gameInfo.resultDetails);
10735     } else {
10736         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10737     }
10738
10739     fclose(f);
10740     return TRUE;
10741 }
10742
10743 /* Save the current game to open file f and close the file */
10744 int
10745 SaveGame(f, dummy, dummy2)
10746      FILE *f;
10747      int dummy;
10748      char *dummy2;
10749 {
10750     if (gameMode == EditPosition) EditPositionDone(TRUE);
10751     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10752     if (appData.oldSaveStyle)
10753       return SaveGameOldStyle(f);
10754     else
10755       return SaveGamePGN(f);
10756 }
10757
10758 /* Save the current position to the given file */
10759 int
10760 SavePositionToFile(filename)
10761      char *filename;
10762 {
10763     FILE *f;
10764     char buf[MSG_SIZ];
10765
10766     if (strcmp(filename, "-") == 0) {
10767         return SavePosition(stdout, 0, NULL);
10768     } else {
10769         f = fopen(filename, "a");
10770         if (f == NULL) {
10771             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10772             DisplayError(buf, errno);
10773             return FALSE;
10774         } else {
10775             SavePosition(f, 0, NULL);
10776             return TRUE;
10777         }
10778     }
10779 }
10780
10781 /* Save the current position to the given open file and close the file */
10782 int
10783 SavePosition(f, dummy, dummy2)
10784      FILE *f;
10785      int dummy;
10786      char *dummy2;
10787 {
10788     time_t tm;
10789     char *fen;
10790     
10791     if (gameMode == EditPosition) EditPositionDone(TRUE);
10792     if (appData.oldSaveStyle) {
10793         tm = time((time_t *) NULL);
10794     
10795         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10796         PrintOpponents(f);
10797         fprintf(f, "[--------------\n");
10798         PrintPosition(f, currentMove);
10799         fprintf(f, "--------------]\n");
10800     } else {
10801         fen = PositionToFEN(currentMove, NULL);
10802         fprintf(f, "%s\n", fen);
10803         free(fen);
10804     }
10805     fclose(f);
10806     return TRUE;
10807 }
10808
10809 void
10810 ReloadCmailMsgEvent(unregister)
10811      int unregister;
10812 {
10813 #if !WIN32
10814     static char *inFilename = NULL;
10815     static char *outFilename;
10816     int i;
10817     struct stat inbuf, outbuf;
10818     int status;
10819     
10820     /* Any registered moves are unregistered if unregister is set, */
10821     /* i.e. invoked by the signal handler */
10822     if (unregister) {
10823         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10824             cmailMoveRegistered[i] = FALSE;
10825             if (cmailCommentList[i] != NULL) {
10826                 free(cmailCommentList[i]);
10827                 cmailCommentList[i] = NULL;
10828             }
10829         }
10830         nCmailMovesRegistered = 0;
10831     }
10832
10833     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10834         cmailResult[i] = CMAIL_NOT_RESULT;
10835     }
10836     nCmailResults = 0;
10837
10838     if (inFilename == NULL) {
10839         /* Because the filenames are static they only get malloced once  */
10840         /* and they never get freed                                      */
10841         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10842         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10843
10844         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10845         sprintf(outFilename, "%s.out", appData.cmailGameName);
10846     }
10847     
10848     status = stat(outFilename, &outbuf);
10849     if (status < 0) {
10850         cmailMailedMove = FALSE;
10851     } else {
10852         status = stat(inFilename, &inbuf);
10853         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10854     }
10855     
10856     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10857        counts the games, notes how each one terminated, etc.
10858        
10859        It would be nice to remove this kludge and instead gather all
10860        the information while building the game list.  (And to keep it
10861        in the game list nodes instead of having a bunch of fixed-size
10862        parallel arrays.)  Note this will require getting each game's
10863        termination from the PGN tags, as the game list builder does
10864        not process the game moves.  --mann
10865        */
10866     cmailMsgLoaded = TRUE;
10867     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10868     
10869     /* Load first game in the file or popup game menu */
10870     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10871
10872 #endif /* !WIN32 */
10873     return;
10874 }
10875
10876 int
10877 RegisterMove()
10878 {
10879     FILE *f;
10880     char string[MSG_SIZ];
10881
10882     if (   cmailMailedMove
10883         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10884         return TRUE;            /* Allow free viewing  */
10885     }
10886
10887     /* Unregister move to ensure that we don't leave RegisterMove        */
10888     /* with the move registered when the conditions for registering no   */
10889     /* longer hold                                                       */
10890     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10891         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10892         nCmailMovesRegistered --;
10893
10894         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
10895           {
10896               free(cmailCommentList[lastLoadGameNumber - 1]);
10897               cmailCommentList[lastLoadGameNumber - 1] = NULL;
10898           }
10899     }
10900
10901     if (cmailOldMove == -1) {
10902         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10903         return FALSE;
10904     }
10905
10906     if (currentMove > cmailOldMove + 1) {
10907         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10908         return FALSE;
10909     }
10910
10911     if (currentMove < cmailOldMove) {
10912         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10913         return FALSE;
10914     }
10915
10916     if (forwardMostMove > currentMove) {
10917         /* Silently truncate extra moves */
10918         TruncateGame();
10919     }
10920
10921     if (   (currentMove == cmailOldMove + 1)
10922         || (   (currentMove == cmailOldMove)
10923             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10924                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10925         if (gameInfo.result != GameUnfinished) {
10926             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10927         }
10928
10929         if (commentList[currentMove] != NULL) {
10930             cmailCommentList[lastLoadGameNumber - 1]
10931               = StrSave(commentList[currentMove]);
10932         }
10933         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10934
10935         if (appData.debugMode)
10936           fprintf(debugFP, "Saving %s for game %d\n",
10937                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10938
10939         sprintf(string,
10940                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10941         
10942         f = fopen(string, "w");
10943         if (appData.oldSaveStyle) {
10944             SaveGameOldStyle(f); /* also closes the file */
10945             
10946             sprintf(string, "%s.pos.out", appData.cmailGameName);
10947             f = fopen(string, "w");
10948             SavePosition(f, 0, NULL); /* also closes the file */
10949         } else {
10950             fprintf(f, "{--------------\n");
10951             PrintPosition(f, currentMove);
10952             fprintf(f, "--------------}\n\n");
10953             
10954             SaveGame(f, 0, NULL); /* also closes the file*/
10955         }
10956         
10957         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10958         nCmailMovesRegistered ++;
10959     } else if (nCmailGames == 1) {
10960         DisplayError(_("You have not made a move yet"), 0);
10961         return FALSE;
10962     }
10963
10964     return TRUE;
10965 }
10966
10967 void
10968 MailMoveEvent()
10969 {
10970 #if !WIN32
10971     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10972     FILE *commandOutput;
10973     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10974     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
10975     int nBuffers;
10976     int i;
10977     int archived;
10978     char *arcDir;
10979
10980     if (! cmailMsgLoaded) {
10981         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10982         return;
10983     }
10984
10985     if (nCmailGames == nCmailResults) {
10986         DisplayError(_("No unfinished games"), 0);
10987         return;
10988     }
10989
10990 #if CMAIL_PROHIBIT_REMAIL
10991     if (cmailMailedMove) {
10992         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);
10993         DisplayError(msg, 0);
10994         return;
10995     }
10996 #endif
10997
10998     if (! (cmailMailedMove || RegisterMove())) return;
10999     
11000     if (   cmailMailedMove
11001         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11002         sprintf(string, partCommandString,
11003                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11004         commandOutput = popen(string, "r");
11005
11006         if (commandOutput == NULL) {
11007             DisplayError(_("Failed to invoke cmail"), 0);
11008         } else {
11009             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11010                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11011             }
11012             if (nBuffers > 1) {
11013                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11014                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11015                 nBytes = MSG_SIZ - 1;
11016             } else {
11017                 (void) memcpy(msg, buffer, nBytes);
11018             }
11019             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11020
11021             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11022                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11023
11024                 archived = TRUE;
11025                 for (i = 0; i < nCmailGames; i ++) {
11026                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11027                         archived = FALSE;
11028                     }
11029                 }
11030                 if (   archived
11031                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11032                         != NULL)) {
11033                     sprintf(buffer, "%s/%s.%s.archive",
11034                             arcDir,
11035                             appData.cmailGameName,
11036                             gameInfo.date);
11037                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11038                     cmailMsgLoaded = FALSE;
11039                 }
11040             }
11041
11042             DisplayInformation(msg);
11043             pclose(commandOutput);
11044         }
11045     } else {
11046         if ((*cmailMsg) != '\0') {
11047             DisplayInformation(cmailMsg);
11048         }
11049     }
11050
11051     return;
11052 #endif /* !WIN32 */
11053 }
11054
11055 char *
11056 CmailMsg()
11057 {
11058 #if WIN32
11059     return NULL;
11060 #else
11061     int  prependComma = 0;
11062     char number[5];
11063     char string[MSG_SIZ];       /* Space for game-list */
11064     int  i;
11065     
11066     if (!cmailMsgLoaded) return "";
11067
11068     if (cmailMailedMove) {
11069         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11070     } else {
11071         /* Create a list of games left */
11072         sprintf(string, "[");
11073         for (i = 0; i < nCmailGames; i ++) {
11074             if (! (   cmailMoveRegistered[i]
11075                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11076                 if (prependComma) {
11077                     sprintf(number, ",%d", i + 1);
11078                 } else {
11079                     sprintf(number, "%d", i + 1);
11080                     prependComma = 1;
11081                 }
11082                 
11083                 strcat(string, number);
11084             }
11085         }
11086         strcat(string, "]");
11087
11088         if (nCmailMovesRegistered + nCmailResults == 0) {
11089             switch (nCmailGames) {
11090               case 1:
11091                 sprintf(cmailMsg,
11092                         _("Still need to make move for game\n"));
11093                 break;
11094                 
11095               case 2:
11096                 sprintf(cmailMsg,
11097                         _("Still need to make moves for both games\n"));
11098                 break;
11099                 
11100               default:
11101                 sprintf(cmailMsg,
11102                         _("Still need to make moves for all %d games\n"),
11103                         nCmailGames);
11104                 break;
11105             }
11106         } else {
11107             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11108               case 1:
11109                 sprintf(cmailMsg,
11110                         _("Still need to make a move for game %s\n"),
11111                         string);
11112                 break;
11113                 
11114               case 0:
11115                 if (nCmailResults == nCmailGames) {
11116                     sprintf(cmailMsg, _("No unfinished games\n"));
11117                 } else {
11118                     sprintf(cmailMsg, _("Ready to send mail\n"));
11119                 }
11120                 break;
11121                 
11122               default:
11123                 sprintf(cmailMsg,
11124                         _("Still need to make moves for games %s\n"),
11125                         string);
11126             }
11127         }
11128     }
11129     return cmailMsg;
11130 #endif /* WIN32 */
11131 }
11132
11133 void
11134 ResetGameEvent()
11135 {
11136     if (gameMode == Training)
11137       SetTrainingModeOff();
11138
11139     Reset(TRUE, TRUE);
11140     cmailMsgLoaded = FALSE;
11141     if (appData.icsActive) {
11142       SendToICS(ics_prefix);
11143       SendToICS("refresh\n");
11144     }
11145 }
11146
11147 void
11148 ExitEvent(status)
11149      int status;
11150 {
11151     exiting++;
11152     if (exiting > 2) {
11153       /* Give up on clean exit */
11154       exit(status);
11155     }
11156     if (exiting > 1) {
11157       /* Keep trying for clean exit */
11158       return;
11159     }
11160
11161     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11162
11163     if (telnetISR != NULL) {
11164       RemoveInputSource(telnetISR);
11165     }
11166     if (icsPR != NoProc) {
11167       DestroyChildProcess(icsPR, TRUE);
11168     }
11169
11170     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11171     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11172
11173     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11174     /* make sure this other one finishes before killing it!                  */
11175     if(endingGame) { int count = 0;
11176         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11177         while(endingGame && count++ < 10) DoSleep(1);
11178         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11179     }
11180
11181     /* Kill off chess programs */
11182     if (first.pr != NoProc) {
11183         ExitAnalyzeMode();
11184         
11185         DoSleep( appData.delayBeforeQuit );
11186         SendToProgram("quit\n", &first);
11187         DoSleep( appData.delayAfterQuit );
11188         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11189     }
11190     if (second.pr != NoProc) {
11191         DoSleep( appData.delayBeforeQuit );
11192         SendToProgram("quit\n", &second);
11193         DoSleep( appData.delayAfterQuit );
11194         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11195     }
11196     if (first.isr != NULL) {
11197         RemoveInputSource(first.isr);
11198     }
11199     if (second.isr != NULL) {
11200         RemoveInputSource(second.isr);
11201     }
11202
11203     ShutDownFrontEnd();
11204     exit(status);
11205 }
11206
11207 void
11208 PauseEvent()
11209 {
11210     if (appData.debugMode)
11211         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11212     if (pausing) {
11213         pausing = FALSE;
11214         ModeHighlight();
11215         if (gameMode == MachinePlaysWhite ||
11216             gameMode == MachinePlaysBlack) {
11217             StartClocks();
11218         } else {
11219             DisplayBothClocks();
11220         }
11221         if (gameMode == PlayFromGameFile) {
11222             if (appData.timeDelay >= 0) 
11223                 AutoPlayGameLoop();
11224         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11225             Reset(FALSE, TRUE);
11226             SendToICS(ics_prefix);
11227             SendToICS("refresh\n");
11228         } else if (currentMove < forwardMostMove) {
11229             ForwardInner(forwardMostMove);
11230         }
11231         pauseExamInvalid = FALSE;
11232     } else {
11233         switch (gameMode) {
11234           default:
11235             return;
11236           case IcsExamining:
11237             pauseExamForwardMostMove = forwardMostMove;
11238             pauseExamInvalid = FALSE;
11239             /* fall through */
11240           case IcsObserving:
11241           case IcsPlayingWhite:
11242           case IcsPlayingBlack:
11243             pausing = TRUE;
11244             ModeHighlight();
11245             return;
11246           case PlayFromGameFile:
11247             (void) StopLoadGameTimer();
11248             pausing = TRUE;
11249             ModeHighlight();
11250             break;
11251           case BeginningOfGame:
11252             if (appData.icsActive) return;
11253             /* else fall through */
11254           case MachinePlaysWhite:
11255           case MachinePlaysBlack:
11256           case TwoMachinesPlay:
11257             if (forwardMostMove == 0)
11258               return;           /* don't pause if no one has moved */
11259             if ((gameMode == MachinePlaysWhite &&
11260                  !WhiteOnMove(forwardMostMove)) ||
11261                 (gameMode == MachinePlaysBlack &&
11262                  WhiteOnMove(forwardMostMove))) {
11263                 StopClocks();
11264             }
11265             pausing = TRUE;
11266             ModeHighlight();
11267             break;
11268         }
11269     }
11270 }
11271
11272 void
11273 EditCommentEvent()
11274 {
11275     char title[MSG_SIZ];
11276
11277     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11278         strcpy(title, _("Edit comment"));
11279     } else {
11280         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11281                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11282                 parseList[currentMove - 1]);
11283     }
11284
11285     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11286 }
11287
11288
11289 void
11290 EditTagsEvent()
11291 {
11292     char *tags = PGNTags(&gameInfo);
11293     EditTagsPopUp(tags);
11294     free(tags);
11295 }
11296
11297 void
11298 AnalyzeModeEvent()
11299 {
11300     if (appData.noChessProgram || gameMode == AnalyzeMode)
11301       return;
11302
11303     if (gameMode != AnalyzeFile) {
11304         if (!appData.icsEngineAnalyze) {
11305                EditGameEvent();
11306                if (gameMode != EditGame) return;
11307         }
11308         ResurrectChessProgram();
11309         SendToProgram("analyze\n", &first);
11310         first.analyzing = TRUE;
11311         /*first.maybeThinking = TRUE;*/
11312         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11313         EngineOutputPopUp();
11314     }
11315     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11316     pausing = FALSE;
11317     ModeHighlight();
11318     SetGameInfo();
11319
11320     StartAnalysisClock();
11321     GetTimeMark(&lastNodeCountTime);
11322     lastNodeCount = 0;
11323 }
11324
11325 void
11326 AnalyzeFileEvent()
11327 {
11328     if (appData.noChessProgram || gameMode == AnalyzeFile)
11329       return;
11330
11331     if (gameMode != AnalyzeMode) {
11332         EditGameEvent();
11333         if (gameMode != EditGame) return;
11334         ResurrectChessProgram();
11335         SendToProgram("analyze\n", &first);
11336         first.analyzing = TRUE;
11337         /*first.maybeThinking = TRUE;*/
11338         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11339         EngineOutputPopUp();
11340     }
11341     gameMode = AnalyzeFile;
11342     pausing = FALSE;
11343     ModeHighlight();
11344     SetGameInfo();
11345
11346     StartAnalysisClock();
11347     GetTimeMark(&lastNodeCountTime);
11348     lastNodeCount = 0;
11349 }
11350
11351 void
11352 MachineWhiteEvent()
11353 {
11354     char buf[MSG_SIZ];
11355     char *bookHit = NULL;
11356
11357     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11358       return;
11359
11360
11361     if (gameMode == PlayFromGameFile || 
11362         gameMode == TwoMachinesPlay  || 
11363         gameMode == Training         || 
11364         gameMode == AnalyzeMode      || 
11365         gameMode == EndOfGame)
11366         EditGameEvent();
11367
11368     if (gameMode == EditPosition) 
11369         EditPositionDone(TRUE);
11370
11371     if (!WhiteOnMove(currentMove)) {
11372         DisplayError(_("It is not White's turn"), 0);
11373         return;
11374     }
11375   
11376     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11377       ExitAnalyzeMode();
11378
11379     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11380         gameMode == AnalyzeFile)
11381         TruncateGame();
11382
11383     ResurrectChessProgram();    /* in case it isn't running */
11384     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11385         gameMode = MachinePlaysWhite;
11386         ResetClocks();
11387     } else
11388     gameMode = MachinePlaysWhite;
11389     pausing = FALSE;
11390     ModeHighlight();
11391     SetGameInfo();
11392     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11393     DisplayTitle(buf);
11394     if (first.sendName) {
11395       sprintf(buf, "name %s\n", gameInfo.black);
11396       SendToProgram(buf, &first);
11397     }
11398     if (first.sendTime) {
11399       if (first.useColors) {
11400         SendToProgram("black\n", &first); /*gnu kludge*/
11401       }
11402       SendTimeRemaining(&first, TRUE);
11403     }
11404     if (first.useColors) {
11405       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11406     }
11407     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11408     SetMachineThinkingEnables();
11409     first.maybeThinking = TRUE;
11410     StartClocks();
11411     firstMove = FALSE;
11412
11413     if (appData.autoFlipView && !flipView) {
11414       flipView = !flipView;
11415       DrawPosition(FALSE, NULL);
11416       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11417     }
11418
11419     if(bookHit) { // [HGM] book: simulate book reply
11420         static char bookMove[MSG_SIZ]; // a bit generous?
11421
11422         programStats.nodes = programStats.depth = programStats.time = 
11423         programStats.score = programStats.got_only_move = 0;
11424         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11425
11426         strcpy(bookMove, "move ");
11427         strcat(bookMove, bookHit);
11428         HandleMachineMove(bookMove, &first);
11429     }
11430 }
11431
11432 void
11433 MachineBlackEvent()
11434 {
11435     char buf[MSG_SIZ];
11436    char *bookHit = NULL;
11437
11438     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11439         return;
11440
11441
11442     if (gameMode == PlayFromGameFile || 
11443         gameMode == TwoMachinesPlay  || 
11444         gameMode == Training         || 
11445         gameMode == AnalyzeMode      || 
11446         gameMode == EndOfGame)
11447         EditGameEvent();
11448
11449     if (gameMode == EditPosition) 
11450         EditPositionDone(TRUE);
11451
11452     if (WhiteOnMove(currentMove)) {
11453         DisplayError(_("It is not Black's turn"), 0);
11454         return;
11455     }
11456     
11457     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11458       ExitAnalyzeMode();
11459
11460     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11461         gameMode == AnalyzeFile)
11462         TruncateGame();
11463
11464     ResurrectChessProgram();    /* in case it isn't running */
11465     gameMode = MachinePlaysBlack;
11466     pausing = FALSE;
11467     ModeHighlight();
11468     SetGameInfo();
11469     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11470     DisplayTitle(buf);
11471     if (first.sendName) {
11472       sprintf(buf, "name %s\n", gameInfo.white);
11473       SendToProgram(buf, &first);
11474     }
11475     if (first.sendTime) {
11476       if (first.useColors) {
11477         SendToProgram("white\n", &first); /*gnu kludge*/
11478       }
11479       SendTimeRemaining(&first, FALSE);
11480     }
11481     if (first.useColors) {
11482       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11483     }
11484     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11485     SetMachineThinkingEnables();
11486     first.maybeThinking = TRUE;
11487     StartClocks();
11488
11489     if (appData.autoFlipView && flipView) {
11490       flipView = !flipView;
11491       DrawPosition(FALSE, NULL);
11492       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11493     }
11494     if(bookHit) { // [HGM] book: simulate book reply
11495         static char bookMove[MSG_SIZ]; // a bit generous?
11496
11497         programStats.nodes = programStats.depth = programStats.time = 
11498         programStats.score = programStats.got_only_move = 0;
11499         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11500
11501         strcpy(bookMove, "move ");
11502         strcat(bookMove, bookHit);
11503         HandleMachineMove(bookMove, &first);
11504     }
11505 }
11506
11507
11508 void
11509 DisplayTwoMachinesTitle()
11510 {
11511     char buf[MSG_SIZ];
11512     if (appData.matchGames > 0) {
11513         if (first.twoMachinesColor[0] == 'w') {
11514             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11515                     gameInfo.white, gameInfo.black,
11516                     first.matchWins, second.matchWins,
11517                     matchGame - 1 - (first.matchWins + second.matchWins));
11518         } else {
11519             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11520                     gameInfo.white, gameInfo.black,
11521                     second.matchWins, first.matchWins,
11522                     matchGame - 1 - (first.matchWins + second.matchWins));
11523         }
11524     } else {
11525         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11526     }
11527     DisplayTitle(buf);
11528 }
11529
11530 void
11531 TwoMachinesEvent P((void))
11532 {
11533     int i;
11534     char buf[MSG_SIZ];
11535     ChessProgramState *onmove;
11536     char *bookHit = NULL;
11537     
11538     if (appData.noChessProgram) return;
11539
11540     switch (gameMode) {
11541       case TwoMachinesPlay:
11542         return;
11543       case MachinePlaysWhite:
11544       case MachinePlaysBlack:
11545         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11546             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11547             return;
11548         }
11549         /* fall through */
11550       case BeginningOfGame:
11551       case PlayFromGameFile:
11552       case EndOfGame:
11553         EditGameEvent();
11554         if (gameMode != EditGame) return;
11555         break;
11556       case EditPosition:
11557         EditPositionDone(TRUE);
11558         break;
11559       case AnalyzeMode:
11560       case AnalyzeFile:
11561         ExitAnalyzeMode();
11562         break;
11563       case EditGame:
11564       default:
11565         break;
11566     }
11567
11568 //    forwardMostMove = currentMove;
11569     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11570     ResurrectChessProgram();    /* in case first program isn't running */
11571
11572     if (second.pr == NULL) {
11573         StartChessProgram(&second);
11574         if (second.protocolVersion == 1) {
11575           TwoMachinesEventIfReady();
11576         } else {
11577           /* kludge: allow timeout for initial "feature" command */
11578           FreezeUI();
11579           DisplayMessage("", _("Starting second chess program"));
11580           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11581         }
11582         return;
11583     }
11584     DisplayMessage("", "");
11585     InitChessProgram(&second, FALSE);
11586     SendToProgram("force\n", &second);
11587     if (startedFromSetupPosition) {
11588         SendBoard(&second, backwardMostMove);
11589     if (appData.debugMode) {
11590         fprintf(debugFP, "Two Machines\n");
11591     }
11592     }
11593     for (i = backwardMostMove; i < forwardMostMove; i++) {
11594         SendMoveToProgram(i, &second);
11595     }
11596
11597     gameMode = TwoMachinesPlay;
11598     pausing = FALSE;
11599     ModeHighlight();
11600     SetGameInfo();
11601     DisplayTwoMachinesTitle();
11602     firstMove = TRUE;
11603     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11604         onmove = &first;
11605     } else {
11606         onmove = &second;
11607     }
11608
11609     SendToProgram(first.computerString, &first);
11610     if (first.sendName) {
11611       sprintf(buf, "name %s\n", second.tidy);
11612       SendToProgram(buf, &first);
11613     }
11614     SendToProgram(second.computerString, &second);
11615     if (second.sendName) {
11616       sprintf(buf, "name %s\n", first.tidy);
11617       SendToProgram(buf, &second);
11618     }
11619
11620     ResetClocks();
11621     if (!first.sendTime || !second.sendTime) {
11622         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11623         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11624     }
11625     if (onmove->sendTime) {
11626       if (onmove->useColors) {
11627         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11628       }
11629       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11630     }
11631     if (onmove->useColors) {
11632       SendToProgram(onmove->twoMachinesColor, onmove);
11633     }
11634     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11635 //    SendToProgram("go\n", onmove);
11636     onmove->maybeThinking = TRUE;
11637     SetMachineThinkingEnables();
11638
11639     StartClocks();
11640
11641     if(bookHit) { // [HGM] book: simulate book reply
11642         static char bookMove[MSG_SIZ]; // a bit generous?
11643
11644         programStats.nodes = programStats.depth = programStats.time = 
11645         programStats.score = programStats.got_only_move = 0;
11646         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11647
11648         strcpy(bookMove, "move ");
11649         strcat(bookMove, bookHit);
11650         savedMessage = bookMove; // args for deferred call
11651         savedState = onmove;
11652         ScheduleDelayedEvent(DeferredBookMove, 1);
11653     }
11654 }
11655
11656 void
11657 TrainingEvent()
11658 {
11659     if (gameMode == Training) {
11660       SetTrainingModeOff();
11661       gameMode = PlayFromGameFile;
11662       DisplayMessage("", _("Training mode off"));
11663     } else {
11664       gameMode = Training;
11665       animateTraining = appData.animate;
11666
11667       /* make sure we are not already at the end of the game */
11668       if (currentMove < forwardMostMove) {
11669         SetTrainingModeOn();
11670         DisplayMessage("", _("Training mode on"));
11671       } else {
11672         gameMode = PlayFromGameFile;
11673         DisplayError(_("Already at end of game"), 0);
11674       }
11675     }
11676     ModeHighlight();
11677 }
11678
11679 void
11680 IcsClientEvent()
11681 {
11682     if (!appData.icsActive) return;
11683     switch (gameMode) {
11684       case IcsPlayingWhite:
11685       case IcsPlayingBlack:
11686       case IcsObserving:
11687       case IcsIdle:
11688       case BeginningOfGame:
11689       case IcsExamining:
11690         return;
11691
11692       case EditGame:
11693         break;
11694
11695       case EditPosition:
11696         EditPositionDone(TRUE);
11697         break;
11698
11699       case AnalyzeMode:
11700       case AnalyzeFile:
11701         ExitAnalyzeMode();
11702         break;
11703         
11704       default:
11705         EditGameEvent();
11706         break;
11707     }
11708
11709     gameMode = IcsIdle;
11710     ModeHighlight();
11711     return;
11712 }
11713
11714
11715 void
11716 EditGameEvent()
11717 {
11718     int i;
11719
11720     switch (gameMode) {
11721       case Training:
11722         SetTrainingModeOff();
11723         break;
11724       case MachinePlaysWhite:
11725       case MachinePlaysBlack:
11726       case BeginningOfGame:
11727         SendToProgram("force\n", &first);
11728         SetUserThinkingEnables();
11729         break;
11730       case PlayFromGameFile:
11731         (void) StopLoadGameTimer();
11732         if (gameFileFP != NULL) {
11733             gameFileFP = NULL;
11734         }
11735         break;
11736       case EditPosition:
11737         EditPositionDone(TRUE);
11738         break;
11739       case AnalyzeMode:
11740       case AnalyzeFile:
11741         ExitAnalyzeMode();
11742         SendToProgram("force\n", &first);
11743         break;
11744       case TwoMachinesPlay:
11745         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11746         ResurrectChessProgram();
11747         SetUserThinkingEnables();
11748         break;
11749       case EndOfGame:
11750         ResurrectChessProgram();
11751         break;
11752       case IcsPlayingBlack:
11753       case IcsPlayingWhite:
11754         DisplayError(_("Warning: You are still playing a game"), 0);
11755         break;
11756       case IcsObserving:
11757         DisplayError(_("Warning: You are still observing a game"), 0);
11758         break;
11759       case IcsExamining:
11760         DisplayError(_("Warning: You are still examining a game"), 0);
11761         break;
11762       case IcsIdle:
11763         break;
11764       case EditGame:
11765       default:
11766         return;
11767     }
11768     
11769     pausing = FALSE;
11770     StopClocks();
11771     first.offeredDraw = second.offeredDraw = 0;
11772
11773     if (gameMode == PlayFromGameFile) {
11774         whiteTimeRemaining = timeRemaining[0][currentMove];
11775         blackTimeRemaining = timeRemaining[1][currentMove];
11776         DisplayTitle("");
11777     }
11778
11779     if (gameMode == MachinePlaysWhite ||
11780         gameMode == MachinePlaysBlack ||
11781         gameMode == TwoMachinesPlay ||
11782         gameMode == EndOfGame) {
11783         i = forwardMostMove;
11784         while (i > currentMove) {
11785             SendToProgram("undo\n", &first);
11786             i--;
11787         }
11788         whiteTimeRemaining = timeRemaining[0][currentMove];
11789         blackTimeRemaining = timeRemaining[1][currentMove];
11790         DisplayBothClocks();
11791         if (whiteFlag || blackFlag) {
11792             whiteFlag = blackFlag = 0;
11793         }
11794         DisplayTitle("");
11795     }           
11796     
11797     gameMode = EditGame;
11798     ModeHighlight();
11799     SetGameInfo();
11800 }
11801
11802
11803 void
11804 EditPositionEvent()
11805 {
11806     if (gameMode == EditPosition) {
11807         EditGameEvent();
11808         return;
11809     }
11810     
11811     EditGameEvent();
11812     if (gameMode != EditGame) return;
11813     
11814     gameMode = EditPosition;
11815     ModeHighlight();
11816     SetGameInfo();
11817     if (currentMove > 0)
11818       CopyBoard(boards[0], boards[currentMove]);
11819     
11820     blackPlaysFirst = !WhiteOnMove(currentMove);
11821     ResetClocks();
11822     currentMove = forwardMostMove = backwardMostMove = 0;
11823     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11824     DisplayMove(-1);
11825 }
11826
11827 void
11828 ExitAnalyzeMode()
11829 {
11830     /* [DM] icsEngineAnalyze - possible call from other functions */
11831     if (appData.icsEngineAnalyze) {
11832         appData.icsEngineAnalyze = FALSE;
11833
11834         DisplayMessage("",_("Close ICS engine analyze..."));
11835     }
11836     if (first.analysisSupport && first.analyzing) {
11837       SendToProgram("exit\n", &first);
11838       first.analyzing = FALSE;
11839     }
11840     thinkOutput[0] = NULLCHAR;
11841 }
11842
11843 void
11844 EditPositionDone(Boolean fakeRights)
11845 {
11846     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11847
11848     startedFromSetupPosition = TRUE;
11849     InitChessProgram(&first, FALSE);
11850     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11851       boards[0][EP_STATUS] = EP_NONE;
11852       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11853     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11854         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11855         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11856       } else boards[0][CASTLING][2] = NoRights;
11857     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11858         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11859         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11860       } else boards[0][CASTLING][5] = NoRights;
11861     }
11862     SendToProgram("force\n", &first);
11863     if (blackPlaysFirst) {
11864         strcpy(moveList[0], "");
11865         strcpy(parseList[0], "");
11866         currentMove = forwardMostMove = backwardMostMove = 1;
11867         CopyBoard(boards[1], boards[0]);
11868     } else {
11869         currentMove = forwardMostMove = backwardMostMove = 0;
11870     }
11871     SendBoard(&first, forwardMostMove);
11872     if (appData.debugMode) {
11873         fprintf(debugFP, "EditPosDone\n");
11874     }
11875     DisplayTitle("");
11876     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11877     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11878     gameMode = EditGame;
11879     ModeHighlight();
11880     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11881     ClearHighlights(); /* [AS] */
11882 }
11883
11884 /* Pause for `ms' milliseconds */
11885 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11886 void
11887 TimeDelay(ms)
11888      long ms;
11889 {
11890     TimeMark m1, m2;
11891
11892     GetTimeMark(&m1);
11893     do {
11894         GetTimeMark(&m2);
11895     } while (SubtractTimeMarks(&m2, &m1) < ms);
11896 }
11897
11898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11899 void
11900 SendMultiLineToICS(buf)
11901      char *buf;
11902 {
11903     char temp[MSG_SIZ+1], *p;
11904     int len;
11905
11906     len = strlen(buf);
11907     if (len > MSG_SIZ)
11908       len = MSG_SIZ;
11909   
11910     strncpy(temp, buf, len);
11911     temp[len] = 0;
11912
11913     p = temp;
11914     while (*p) {
11915         if (*p == '\n' || *p == '\r')
11916           *p = ' ';
11917         ++p;
11918     }
11919
11920     strcat(temp, "\n");
11921     SendToICS(temp);
11922     SendToPlayer(temp, strlen(temp));
11923 }
11924
11925 void
11926 SetWhiteToPlayEvent()
11927 {
11928     if (gameMode == EditPosition) {
11929         blackPlaysFirst = FALSE;
11930         DisplayBothClocks();    /* works because currentMove is 0 */
11931     } else if (gameMode == IcsExamining) {
11932         SendToICS(ics_prefix);
11933         SendToICS("tomove white\n");
11934     }
11935 }
11936
11937 void
11938 SetBlackToPlayEvent()
11939 {
11940     if (gameMode == EditPosition) {
11941         blackPlaysFirst = TRUE;
11942         currentMove = 1;        /* kludge */
11943         DisplayBothClocks();
11944         currentMove = 0;
11945     } else if (gameMode == IcsExamining) {
11946         SendToICS(ics_prefix);
11947         SendToICS("tomove black\n");
11948     }
11949 }
11950
11951 void
11952 EditPositionMenuEvent(selection, x, y)
11953      ChessSquare selection;
11954      int x, y;
11955 {
11956     char buf[MSG_SIZ];
11957     ChessSquare piece = boards[0][y][x];
11958
11959     if (gameMode != EditPosition && gameMode != IcsExamining) return;
11960
11961     switch (selection) {
11962       case ClearBoard:
11963         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11964             SendToICS(ics_prefix);
11965             SendToICS("bsetup clear\n");
11966         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11967             SendToICS(ics_prefix);
11968             SendToICS("clearboard\n");
11969         } else {
11970             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11971                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11972                 for (y = 0; y < BOARD_HEIGHT; y++) {
11973                     if (gameMode == IcsExamining) {
11974                         if (boards[currentMove][y][x] != EmptySquare) {
11975                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
11976                                     AAA + x, ONE + y);
11977                             SendToICS(buf);
11978                         }
11979                     } else {
11980                         boards[0][y][x] = p;
11981                     }
11982                 }
11983             }
11984         }
11985         if (gameMode == EditPosition) {
11986             DrawPosition(FALSE, boards[0]);
11987         }
11988         break;
11989
11990       case WhitePlay:
11991         SetWhiteToPlayEvent();
11992         break;
11993
11994       case BlackPlay:
11995         SetBlackToPlayEvent();
11996         break;
11997
11998       case EmptySquare:
11999         if (gameMode == IcsExamining) {
12000             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12001             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12002             SendToICS(buf);
12003         } else {
12004             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12005                 if(x == BOARD_LEFT-2) {
12006                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12007                     boards[0][y][1] = 0;
12008                 } else
12009                 if(x == BOARD_RGHT+1) {
12010                     if(y >= gameInfo.holdingsSize) break;
12011                     boards[0][y][BOARD_WIDTH-2] = 0;
12012                 } else break;
12013             }
12014             boards[0][y][x] = EmptySquare;
12015             DrawPosition(FALSE, boards[0]);
12016         }
12017         break;
12018
12019       case PromotePiece:
12020         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12021            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12022             selection = (ChessSquare) (PROMOTED piece);
12023         } else if(piece == EmptySquare) selection = WhiteSilver;
12024         else selection = (ChessSquare)((int)piece - 1);
12025         goto defaultlabel;
12026
12027       case DemotePiece:
12028         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12029            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12030             selection = (ChessSquare) (DEMOTED piece);
12031         } else if(piece == EmptySquare) selection = BlackSilver;
12032         else selection = (ChessSquare)((int)piece + 1);       
12033         goto defaultlabel;
12034
12035       case WhiteQueen:
12036       case BlackQueen:
12037         if(gameInfo.variant == VariantShatranj ||
12038            gameInfo.variant == VariantXiangqi  ||
12039            gameInfo.variant == VariantCourier  ||
12040            gameInfo.variant == VariantMakruk     )
12041             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12042         goto defaultlabel;
12043
12044       case WhiteKing:
12045       case BlackKing:
12046         if(gameInfo.variant == VariantXiangqi)
12047             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12048         if(gameInfo.variant == VariantKnightmate)
12049             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12050       default:
12051         defaultlabel:
12052         if (gameMode == IcsExamining) {
12053             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12054             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12055                     PieceToChar(selection), AAA + x, ONE + y);
12056             SendToICS(buf);
12057         } else {
12058             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12059                 int n;
12060                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12061                     n = PieceToNumber(selection - BlackPawn);
12062                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12063                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12064                     boards[0][BOARD_HEIGHT-1-n][1]++;
12065                 } else
12066                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12067                     n = PieceToNumber(selection);
12068                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12069                     boards[0][n][BOARD_WIDTH-1] = selection;
12070                     boards[0][n][BOARD_WIDTH-2]++;
12071                 }
12072             } else
12073             boards[0][y][x] = selection;
12074             DrawPosition(TRUE, boards[0]);
12075         }
12076         break;
12077     }
12078 }
12079
12080
12081 void
12082 DropMenuEvent(selection, x, y)
12083      ChessSquare selection;
12084      int x, y;
12085 {
12086     ChessMove moveType;
12087
12088     switch (gameMode) {
12089       case IcsPlayingWhite:
12090       case MachinePlaysBlack:
12091         if (!WhiteOnMove(currentMove)) {
12092             DisplayMoveError(_("It is Black's turn"));
12093             return;
12094         }
12095         moveType = WhiteDrop;
12096         break;
12097       case IcsPlayingBlack:
12098       case MachinePlaysWhite:
12099         if (WhiteOnMove(currentMove)) {
12100             DisplayMoveError(_("It is White's turn"));
12101             return;
12102         }
12103         moveType = BlackDrop;
12104         break;
12105       case EditGame:
12106         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12107         break;
12108       default:
12109         return;
12110     }
12111
12112     if (moveType == BlackDrop && selection < BlackPawn) {
12113       selection = (ChessSquare) ((int) selection
12114                                  + (int) BlackPawn - (int) WhitePawn);
12115     }
12116     if (boards[currentMove][y][x] != EmptySquare) {
12117         DisplayMoveError(_("That square is occupied"));
12118         return;
12119     }
12120
12121     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12122 }
12123
12124 void
12125 AcceptEvent()
12126 {
12127     /* Accept a pending offer of any kind from opponent */
12128     
12129     if (appData.icsActive) {
12130         SendToICS(ics_prefix);
12131         SendToICS("accept\n");
12132     } else if (cmailMsgLoaded) {
12133         if (currentMove == cmailOldMove &&
12134             commentList[cmailOldMove] != NULL &&
12135             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12136                    "Black offers a draw" : "White offers a draw")) {
12137             TruncateGame();
12138             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12139             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12140         } else {
12141             DisplayError(_("There is no pending offer on this move"), 0);
12142             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12143         }
12144     } else {
12145         /* Not used for offers from chess program */
12146     }
12147 }
12148
12149 void
12150 DeclineEvent()
12151 {
12152     /* Decline a pending offer of any kind from opponent */
12153     
12154     if (appData.icsActive) {
12155         SendToICS(ics_prefix);
12156         SendToICS("decline\n");
12157     } else if (cmailMsgLoaded) {
12158         if (currentMove == cmailOldMove &&
12159             commentList[cmailOldMove] != NULL &&
12160             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12161                    "Black offers a draw" : "White offers a draw")) {
12162 #ifdef NOTDEF
12163             AppendComment(cmailOldMove, "Draw declined", TRUE);
12164             DisplayComment(cmailOldMove - 1, "Draw declined");
12165 #endif /*NOTDEF*/
12166         } else {
12167             DisplayError(_("There is no pending offer on this move"), 0);
12168         }
12169     } else {
12170         /* Not used for offers from chess program */
12171     }
12172 }
12173
12174 void
12175 RematchEvent()
12176 {
12177     /* Issue ICS rematch command */
12178     if (appData.icsActive) {
12179         SendToICS(ics_prefix);
12180         SendToICS("rematch\n");
12181     }
12182 }
12183
12184 void
12185 CallFlagEvent()
12186 {
12187     /* Call your opponent's flag (claim a win on time) */
12188     if (appData.icsActive) {
12189         SendToICS(ics_prefix);
12190         SendToICS("flag\n");
12191     } else {
12192         switch (gameMode) {
12193           default:
12194             return;
12195           case MachinePlaysWhite:
12196             if (whiteFlag) {
12197                 if (blackFlag)
12198                   GameEnds(GameIsDrawn, "Both players ran out of time",
12199                            GE_PLAYER);
12200                 else
12201                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12202             } else {
12203                 DisplayError(_("Your opponent is not out of time"), 0);
12204             }
12205             break;
12206           case MachinePlaysBlack:
12207             if (blackFlag) {
12208                 if (whiteFlag)
12209                   GameEnds(GameIsDrawn, "Both players ran out of time",
12210                            GE_PLAYER);
12211                 else
12212                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12213             } else {
12214                 DisplayError(_("Your opponent is not out of time"), 0);
12215             }
12216             break;
12217         }
12218     }
12219 }
12220
12221 void
12222 DrawEvent()
12223 {
12224     /* Offer draw or accept pending draw offer from opponent */
12225     
12226     if (appData.icsActive) {
12227         /* Note: tournament rules require draw offers to be
12228            made after you make your move but before you punch
12229            your clock.  Currently ICS doesn't let you do that;
12230            instead, you immediately punch your clock after making
12231            a move, but you can offer a draw at any time. */
12232         
12233         SendToICS(ics_prefix);
12234         SendToICS("draw\n");
12235         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12236     } else if (cmailMsgLoaded) {
12237         if (currentMove == cmailOldMove &&
12238             commentList[cmailOldMove] != NULL &&
12239             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12240                    "Black offers a draw" : "White offers a draw")) {
12241             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12242             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12243         } else if (currentMove == cmailOldMove + 1) {
12244             char *offer = WhiteOnMove(cmailOldMove) ?
12245               "White offers a draw" : "Black offers a draw";
12246             AppendComment(currentMove, offer, TRUE);
12247             DisplayComment(currentMove - 1, offer);
12248             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12249         } else {
12250             DisplayError(_("You must make your move before offering a draw"), 0);
12251             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12252         }
12253     } else if (first.offeredDraw) {
12254         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12255     } else {
12256         if (first.sendDrawOffers) {
12257             SendToProgram("draw\n", &first);
12258             userOfferedDraw = TRUE;
12259         }
12260     }
12261 }
12262
12263 void
12264 AdjournEvent()
12265 {
12266     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12267     
12268     if (appData.icsActive) {
12269         SendToICS(ics_prefix);
12270         SendToICS("adjourn\n");
12271     } else {
12272         /* Currently GNU Chess doesn't offer or accept Adjourns */
12273     }
12274 }
12275
12276
12277 void
12278 AbortEvent()
12279 {
12280     /* Offer Abort or accept pending Abort offer from opponent */
12281     
12282     if (appData.icsActive) {
12283         SendToICS(ics_prefix);
12284         SendToICS("abort\n");
12285     } else {
12286         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12287     }
12288 }
12289
12290 void
12291 ResignEvent()
12292 {
12293     /* Resign.  You can do this even if it's not your turn. */
12294     
12295     if (appData.icsActive) {
12296         SendToICS(ics_prefix);
12297         SendToICS("resign\n");
12298     } else {
12299         switch (gameMode) {
12300           case MachinePlaysWhite:
12301             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12302             break;
12303           case MachinePlaysBlack:
12304             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12305             break;
12306           case EditGame:
12307             if (cmailMsgLoaded) {
12308                 TruncateGame();
12309                 if (WhiteOnMove(cmailOldMove)) {
12310                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12311                 } else {
12312                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12313                 }
12314                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12315             }
12316             break;
12317           default:
12318             break;
12319         }
12320     }
12321 }
12322
12323
12324 void
12325 StopObservingEvent()
12326 {
12327     /* Stop observing current games */
12328     SendToICS(ics_prefix);
12329     SendToICS("unobserve\n");
12330 }
12331
12332 void
12333 StopExaminingEvent()
12334 {
12335     /* Stop observing current game */
12336     SendToICS(ics_prefix);
12337     SendToICS("unexamine\n");
12338 }
12339
12340 void
12341 ForwardInner(target)
12342      int target;
12343 {
12344     int limit;
12345
12346     if (appData.debugMode)
12347         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12348                 target, currentMove, forwardMostMove);
12349
12350     if (gameMode == EditPosition)
12351       return;
12352
12353     if (gameMode == PlayFromGameFile && !pausing)
12354       PauseEvent();
12355     
12356     if (gameMode == IcsExamining && pausing)
12357       limit = pauseExamForwardMostMove;
12358     else
12359       limit = forwardMostMove;
12360     
12361     if (target > limit) target = limit;
12362
12363     if (target > 0 && moveList[target - 1][0]) {
12364         int fromX, fromY, toX, toY;
12365         toX = moveList[target - 1][2] - AAA;
12366         toY = moveList[target - 1][3] - ONE;
12367         if (moveList[target - 1][1] == '@') {
12368             if (appData.highlightLastMove) {
12369                 SetHighlights(-1, -1, toX, toY);
12370             }
12371         } else {
12372             fromX = moveList[target - 1][0] - AAA;
12373             fromY = moveList[target - 1][1] - ONE;
12374             if (target == currentMove + 1) {
12375                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12376             }
12377             if (appData.highlightLastMove) {
12378                 SetHighlights(fromX, fromY, toX, toY);
12379             }
12380         }
12381     }
12382     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12383         gameMode == Training || gameMode == PlayFromGameFile || 
12384         gameMode == AnalyzeFile) {
12385         while (currentMove < target) {
12386             SendMoveToProgram(currentMove++, &first);
12387         }
12388     } else {
12389         currentMove = target;
12390     }
12391     
12392     if (gameMode == EditGame || gameMode == EndOfGame) {
12393         whiteTimeRemaining = timeRemaining[0][currentMove];
12394         blackTimeRemaining = timeRemaining[1][currentMove];
12395     }
12396     DisplayBothClocks();
12397     DisplayMove(currentMove - 1);
12398     DrawPosition(FALSE, boards[currentMove]);
12399     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12400     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12401         DisplayComment(currentMove - 1, commentList[currentMove]);
12402     }
12403 }
12404
12405
12406 void
12407 ForwardEvent()
12408 {
12409     if (gameMode == IcsExamining && !pausing) {
12410         SendToICS(ics_prefix);
12411         SendToICS("forward\n");
12412     } else {
12413         ForwardInner(currentMove + 1);
12414     }
12415 }
12416
12417 void
12418 ToEndEvent()
12419 {
12420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12421         /* to optimze, we temporarily turn off analysis mode while we feed
12422          * the remaining moves to the engine. Otherwise we get analysis output
12423          * after each move.
12424          */ 
12425         if (first.analysisSupport) {
12426           SendToProgram("exit\nforce\n", &first);
12427           first.analyzing = FALSE;
12428         }
12429     }
12430         
12431     if (gameMode == IcsExamining && !pausing) {
12432         SendToICS(ics_prefix);
12433         SendToICS("forward 999999\n");
12434     } else {
12435         ForwardInner(forwardMostMove);
12436     }
12437
12438     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12439         /* we have fed all the moves, so reactivate analysis mode */
12440         SendToProgram("analyze\n", &first);
12441         first.analyzing = TRUE;
12442         /*first.maybeThinking = TRUE;*/
12443         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12444     }
12445 }
12446
12447 void
12448 BackwardInner(target)
12449      int target;
12450 {
12451     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12452
12453     if (appData.debugMode)
12454         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12455                 target, currentMove, forwardMostMove);
12456
12457     if (gameMode == EditPosition) return;
12458     if (currentMove <= backwardMostMove) {
12459         ClearHighlights();
12460         DrawPosition(full_redraw, boards[currentMove]);
12461         return;
12462     }
12463     if (gameMode == PlayFromGameFile && !pausing)
12464       PauseEvent();
12465     
12466     if (moveList[target][0]) {
12467         int fromX, fromY, toX, toY;
12468         toX = moveList[target][2] - AAA;
12469         toY = moveList[target][3] - ONE;
12470         if (moveList[target][1] == '@') {
12471             if (appData.highlightLastMove) {
12472                 SetHighlights(-1, -1, toX, toY);
12473             }
12474         } else {
12475             fromX = moveList[target][0] - AAA;
12476             fromY = moveList[target][1] - ONE;
12477             if (target == currentMove - 1) {
12478                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12479             }
12480             if (appData.highlightLastMove) {
12481                 SetHighlights(fromX, fromY, toX, toY);
12482             }
12483         }
12484     }
12485     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12486         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12487         while (currentMove > target) {
12488             SendToProgram("undo\n", &first);
12489             currentMove--;
12490         }
12491     } else {
12492         currentMove = target;
12493     }
12494     
12495     if (gameMode == EditGame || gameMode == EndOfGame) {
12496         whiteTimeRemaining = timeRemaining[0][currentMove];
12497         blackTimeRemaining = timeRemaining[1][currentMove];
12498     }
12499     DisplayBothClocks();
12500     DisplayMove(currentMove - 1);
12501     DrawPosition(full_redraw, boards[currentMove]);
12502     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12503     // [HGM] PV info: routine tests if comment empty
12504     DisplayComment(currentMove - 1, commentList[currentMove]);
12505 }
12506
12507 void
12508 BackwardEvent()
12509 {
12510     if (gameMode == IcsExamining && !pausing) {
12511         SendToICS(ics_prefix);
12512         SendToICS("backward\n");
12513     } else {
12514         BackwardInner(currentMove - 1);
12515     }
12516 }
12517
12518 void
12519 ToStartEvent()
12520 {
12521     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12522         /* to optimize, we temporarily turn off analysis mode while we undo
12523          * all the moves. Otherwise we get analysis output after each undo.
12524          */ 
12525         if (first.analysisSupport) {
12526           SendToProgram("exit\nforce\n", &first);
12527           first.analyzing = FALSE;
12528         }
12529     }
12530
12531     if (gameMode == IcsExamining && !pausing) {
12532         SendToICS(ics_prefix);
12533         SendToICS("backward 999999\n");
12534     } else {
12535         BackwardInner(backwardMostMove);
12536     }
12537
12538     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12539         /* we have fed all the moves, so reactivate analysis mode */
12540         SendToProgram("analyze\n", &first);
12541         first.analyzing = TRUE;
12542         /*first.maybeThinking = TRUE;*/
12543         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12544     }
12545 }
12546
12547 void
12548 ToNrEvent(int to)
12549 {
12550   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12551   if (to >= forwardMostMove) to = forwardMostMove;
12552   if (to <= backwardMostMove) to = backwardMostMove;
12553   if (to < currentMove) {
12554     BackwardInner(to);
12555   } else {
12556     ForwardInner(to);
12557   }
12558 }
12559
12560 void
12561 RevertEvent()
12562 {
12563     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12564         return;
12565     }
12566     if (gameMode != IcsExamining) {
12567         DisplayError(_("You are not examining a game"), 0);
12568         return;
12569     }
12570     if (pausing) {
12571         DisplayError(_("You can't revert while pausing"), 0);
12572         return;
12573     }
12574     SendToICS(ics_prefix);
12575     SendToICS("revert\n");
12576 }
12577
12578 void
12579 RetractMoveEvent()
12580 {
12581     switch (gameMode) {
12582       case MachinePlaysWhite:
12583       case MachinePlaysBlack:
12584         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12585             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12586             return;
12587         }
12588         if (forwardMostMove < 2) return;
12589         currentMove = forwardMostMove = forwardMostMove - 2;
12590         whiteTimeRemaining = timeRemaining[0][currentMove];
12591         blackTimeRemaining = timeRemaining[1][currentMove];
12592         DisplayBothClocks();
12593         DisplayMove(currentMove - 1);
12594         ClearHighlights();/*!! could figure this out*/
12595         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12596         SendToProgram("remove\n", &first);
12597         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12598         break;
12599
12600       case BeginningOfGame:
12601       default:
12602         break;
12603
12604       case IcsPlayingWhite:
12605       case IcsPlayingBlack:
12606         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12607             SendToICS(ics_prefix);
12608             SendToICS("takeback 2\n");
12609         } else {
12610             SendToICS(ics_prefix);
12611             SendToICS("takeback 1\n");
12612         }
12613         break;
12614     }
12615 }
12616
12617 void
12618 MoveNowEvent()
12619 {
12620     ChessProgramState *cps;
12621
12622     switch (gameMode) {
12623       case MachinePlaysWhite:
12624         if (!WhiteOnMove(forwardMostMove)) {
12625             DisplayError(_("It is your turn"), 0);
12626             return;
12627         }
12628         cps = &first;
12629         break;
12630       case MachinePlaysBlack:
12631         if (WhiteOnMove(forwardMostMove)) {
12632             DisplayError(_("It is your turn"), 0);
12633             return;
12634         }
12635         cps = &first;
12636         break;
12637       case TwoMachinesPlay:
12638         if (WhiteOnMove(forwardMostMove) ==
12639             (first.twoMachinesColor[0] == 'w')) {
12640             cps = &first;
12641         } else {
12642             cps = &second;
12643         }
12644         break;
12645       case BeginningOfGame:
12646       default:
12647         return;
12648     }
12649     SendToProgram("?\n", cps);
12650 }
12651
12652 void
12653 TruncateGameEvent()
12654 {
12655     EditGameEvent();
12656     if (gameMode != EditGame) return;
12657     TruncateGame();
12658 }
12659
12660 void
12661 TruncateGame()
12662 {
12663     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12664     if (forwardMostMove > currentMove) {
12665         if (gameInfo.resultDetails != NULL) {
12666             free(gameInfo.resultDetails);
12667             gameInfo.resultDetails = NULL;
12668             gameInfo.result = GameUnfinished;
12669         }
12670         forwardMostMove = currentMove;
12671         HistorySet(parseList, backwardMostMove, forwardMostMove,
12672                    currentMove-1);
12673     }
12674 }
12675
12676 void
12677 HintEvent()
12678 {
12679     if (appData.noChessProgram) return;
12680     switch (gameMode) {
12681       case MachinePlaysWhite:
12682         if (WhiteOnMove(forwardMostMove)) {
12683             DisplayError(_("Wait until your turn"), 0);
12684             return;
12685         }
12686         break;
12687       case BeginningOfGame:
12688       case MachinePlaysBlack:
12689         if (!WhiteOnMove(forwardMostMove)) {
12690             DisplayError(_("Wait until your turn"), 0);
12691             return;
12692         }
12693         break;
12694       default:
12695         DisplayError(_("No hint available"), 0);
12696         return;
12697     }
12698     SendToProgram("hint\n", &first);
12699     hintRequested = TRUE;
12700 }
12701
12702 void
12703 BookEvent()
12704 {
12705     if (appData.noChessProgram) return;
12706     switch (gameMode) {
12707       case MachinePlaysWhite:
12708         if (WhiteOnMove(forwardMostMove)) {
12709             DisplayError(_("Wait until your turn"), 0);
12710             return;
12711         }
12712         break;
12713       case BeginningOfGame:
12714       case MachinePlaysBlack:
12715         if (!WhiteOnMove(forwardMostMove)) {
12716             DisplayError(_("Wait until your turn"), 0);
12717             return;
12718         }
12719         break;
12720       case EditPosition:
12721         EditPositionDone(TRUE);
12722         break;
12723       case TwoMachinesPlay:
12724         return;
12725       default:
12726         break;
12727     }
12728     SendToProgram("bk\n", &first);
12729     bookOutput[0] = NULLCHAR;
12730     bookRequested = TRUE;
12731 }
12732
12733 void
12734 AboutGameEvent()
12735 {
12736     char *tags = PGNTags(&gameInfo);
12737     TagsPopUp(tags, CmailMsg());
12738     free(tags);
12739 }
12740
12741 /* end button procedures */
12742
12743 void
12744 PrintPosition(fp, move)
12745      FILE *fp;
12746      int move;
12747 {
12748     int i, j;
12749     
12750     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12751         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12752             char c = PieceToChar(boards[move][i][j]);
12753             fputc(c == 'x' ? '.' : c, fp);
12754             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12755         }
12756     }
12757     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12758       fprintf(fp, "white to play\n");
12759     else
12760       fprintf(fp, "black to play\n");
12761 }
12762
12763 void
12764 PrintOpponents(fp)
12765      FILE *fp;
12766 {
12767     if (gameInfo.white != NULL) {
12768         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12769     } else {
12770         fprintf(fp, "\n");
12771     }
12772 }
12773
12774 /* Find last component of program's own name, using some heuristics */
12775 void
12776 TidyProgramName(prog, host, buf)
12777      char *prog, *host, buf[MSG_SIZ];
12778 {
12779     char *p, *q;
12780     int local = (strcmp(host, "localhost") == 0);
12781     while (!local && (p = strchr(prog, ';')) != NULL) {
12782         p++;
12783         while (*p == ' ') p++;
12784         prog = p;
12785     }
12786     if (*prog == '"' || *prog == '\'') {
12787         q = strchr(prog + 1, *prog);
12788     } else {
12789         q = strchr(prog, ' ');
12790     }
12791     if (q == NULL) q = prog + strlen(prog);
12792     p = q;
12793     while (p >= prog && *p != '/' && *p != '\\') p--;
12794     p++;
12795     if(p == prog && *p == '"') p++;
12796     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12797     memcpy(buf, p, q - p);
12798     buf[q - p] = NULLCHAR;
12799     if (!local) {
12800         strcat(buf, "@");
12801         strcat(buf, host);
12802     }
12803 }
12804
12805 char *
12806 TimeControlTagValue()
12807 {
12808     char buf[MSG_SIZ];
12809     if (!appData.clockMode) {
12810         strcpy(buf, "-");
12811     } else if (movesPerSession > 0) {
12812         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12813     } else if (timeIncrement == 0) {
12814         sprintf(buf, "%ld", timeControl/1000);
12815     } else {
12816         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12817     }
12818     return StrSave(buf);
12819 }
12820
12821 void
12822 SetGameInfo()
12823 {
12824     /* This routine is used only for certain modes */
12825     VariantClass v = gameInfo.variant;
12826     ChessMove r = GameUnfinished;
12827     char *p = NULL;
12828
12829     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12830         r = gameInfo.result; 
12831         p = gameInfo.resultDetails; 
12832         gameInfo.resultDetails = NULL;
12833     }
12834     ClearGameInfo(&gameInfo);
12835     gameInfo.variant = v;
12836
12837     switch (gameMode) {
12838       case MachinePlaysWhite:
12839         gameInfo.event = StrSave( appData.pgnEventHeader );
12840         gameInfo.site = StrSave(HostName());
12841         gameInfo.date = PGNDate();
12842         gameInfo.round = StrSave("-");
12843         gameInfo.white = StrSave(first.tidy);
12844         gameInfo.black = StrSave(UserName());
12845         gameInfo.timeControl = TimeControlTagValue();
12846         break;
12847
12848       case MachinePlaysBlack:
12849         gameInfo.event = StrSave( appData.pgnEventHeader );
12850         gameInfo.site = StrSave(HostName());
12851         gameInfo.date = PGNDate();
12852         gameInfo.round = StrSave("-");
12853         gameInfo.white = StrSave(UserName());
12854         gameInfo.black = StrSave(first.tidy);
12855         gameInfo.timeControl = TimeControlTagValue();
12856         break;
12857
12858       case TwoMachinesPlay:
12859         gameInfo.event = StrSave( appData.pgnEventHeader );
12860         gameInfo.site = StrSave(HostName());
12861         gameInfo.date = PGNDate();
12862         if (matchGame > 0) {
12863             char buf[MSG_SIZ];
12864             sprintf(buf, "%d", matchGame);
12865             gameInfo.round = StrSave(buf);
12866         } else {
12867             gameInfo.round = StrSave("-");
12868         }
12869         if (first.twoMachinesColor[0] == 'w') {
12870             gameInfo.white = StrSave(first.tidy);
12871             gameInfo.black = StrSave(second.tidy);
12872         } else {
12873             gameInfo.white = StrSave(second.tidy);
12874             gameInfo.black = StrSave(first.tidy);
12875         }
12876         gameInfo.timeControl = TimeControlTagValue();
12877         break;
12878
12879       case EditGame:
12880         gameInfo.event = StrSave("Edited game");
12881         gameInfo.site = StrSave(HostName());
12882         gameInfo.date = PGNDate();
12883         gameInfo.round = StrSave("-");
12884         gameInfo.white = StrSave("-");
12885         gameInfo.black = StrSave("-");
12886         gameInfo.result = r;
12887         gameInfo.resultDetails = p;
12888         break;
12889
12890       case EditPosition:
12891         gameInfo.event = StrSave("Edited position");
12892         gameInfo.site = StrSave(HostName());
12893         gameInfo.date = PGNDate();
12894         gameInfo.round = StrSave("-");
12895         gameInfo.white = StrSave("-");
12896         gameInfo.black = StrSave("-");
12897         break;
12898
12899       case IcsPlayingWhite:
12900       case IcsPlayingBlack:
12901       case IcsObserving:
12902       case IcsExamining:
12903         break;
12904
12905       case PlayFromGameFile:
12906         gameInfo.event = StrSave("Game from non-PGN file");
12907         gameInfo.site = StrSave(HostName());
12908         gameInfo.date = PGNDate();
12909         gameInfo.round = StrSave("-");
12910         gameInfo.white = StrSave("?");
12911         gameInfo.black = StrSave("?");
12912         break;
12913
12914       default:
12915         break;
12916     }
12917 }
12918
12919 void
12920 ReplaceComment(index, text)
12921      int index;
12922      char *text;
12923 {
12924     int len;
12925
12926     while (*text == '\n') text++;
12927     len = strlen(text);
12928     while (len > 0 && text[len - 1] == '\n') len--;
12929
12930     if (commentList[index] != NULL)
12931       free(commentList[index]);
12932
12933     if (len == 0) {
12934         commentList[index] = NULL;
12935         return;
12936     }
12937   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12938       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12939       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12940     commentList[index] = (char *) malloc(len + 2);
12941     strncpy(commentList[index], text, len);
12942     commentList[index][len] = '\n';
12943     commentList[index][len + 1] = NULLCHAR;
12944   } else { 
12945     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12946     char *p;
12947     commentList[index] = (char *) malloc(len + 6);
12948     strcpy(commentList[index], "{\n");
12949     strncpy(commentList[index]+2, text, len);
12950     commentList[index][len+2] = NULLCHAR;
12951     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12952     strcat(commentList[index], "\n}\n");
12953   }
12954 }
12955
12956 void
12957 CrushCRs(text)
12958      char *text;
12959 {
12960   char *p = text;
12961   char *q = text;
12962   char ch;
12963
12964   do {
12965     ch = *p++;
12966     if (ch == '\r') continue;
12967     *q++ = ch;
12968   } while (ch != '\0');
12969 }
12970
12971 void
12972 AppendComment(index, text, addBraces)
12973      int index;
12974      char *text;
12975      Boolean addBraces; // [HGM] braces: tells if we should add {}
12976 {
12977     int oldlen, len;
12978     char *old;
12979
12980 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12981     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12982
12983     CrushCRs(text);
12984     while (*text == '\n') text++;
12985     len = strlen(text);
12986     while (len > 0 && text[len - 1] == '\n') len--;
12987
12988     if (len == 0) return;
12989
12990     if (commentList[index] != NULL) {
12991         old = commentList[index];
12992         oldlen = strlen(old);
12993         while(commentList[index][oldlen-1] ==  '\n')
12994           commentList[index][--oldlen] = NULLCHAR;
12995         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12996         strcpy(commentList[index], old);
12997         free(old);
12998         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12999         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13000           if(addBraces) addBraces = FALSE; else { text++; len--; }
13001           while (*text == '\n') { text++; len--; }
13002           commentList[index][--oldlen] = NULLCHAR;
13003       }
13004         if(addBraces) strcat(commentList[index], "\n{\n");
13005         else          strcat(commentList[index], "\n");
13006         strcat(commentList[index], text);
13007         if(addBraces) strcat(commentList[index], "\n}\n");
13008         else          strcat(commentList[index], "\n");
13009     } else {
13010         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13011         if(addBraces)
13012              strcpy(commentList[index], "{\n");
13013         else commentList[index][0] = NULLCHAR;
13014         strcat(commentList[index], text);
13015         strcat(commentList[index], "\n");
13016         if(addBraces) strcat(commentList[index], "}\n");
13017     }
13018 }
13019
13020 static char * FindStr( char * text, char * sub_text )
13021 {
13022     char * result = strstr( text, sub_text );
13023
13024     if( result != NULL ) {
13025         result += strlen( sub_text );
13026     }
13027
13028     return result;
13029 }
13030
13031 /* [AS] Try to extract PV info from PGN comment */
13032 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13033 char *GetInfoFromComment( int index, char * text )
13034 {
13035     char * sep = text;
13036
13037     if( text != NULL && index > 0 ) {
13038         int score = 0;
13039         int depth = 0;
13040         int time = -1, sec = 0, deci;
13041         char * s_eval = FindStr( text, "[%eval " );
13042         char * s_emt = FindStr( text, "[%emt " );
13043
13044         if( s_eval != NULL || s_emt != NULL ) {
13045             /* New style */
13046             char delim;
13047
13048             if( s_eval != NULL ) {
13049                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13050                     return text;
13051                 }
13052
13053                 if( delim != ']' ) {
13054                     return text;
13055                 }
13056             }
13057
13058             if( s_emt != NULL ) {
13059             }
13060                 return text;
13061         }
13062         else {
13063             /* We expect something like: [+|-]nnn.nn/dd */
13064             int score_lo = 0;
13065
13066             if(*text != '{') return text; // [HGM] braces: must be normal comment
13067
13068             sep = strchr( text, '/' );
13069             if( sep == NULL || sep < (text+4) ) {
13070                 return text;
13071             }
13072
13073             time = -1; sec = -1; deci = -1;
13074             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13075                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13076                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13077                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13078                 return text;
13079             }
13080
13081             if( score_lo < 0 || score_lo >= 100 ) {
13082                 return text;
13083             }
13084
13085             if(sec >= 0) time = 600*time + 10*sec; else
13086             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13087
13088             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13089
13090             /* [HGM] PV time: now locate end of PV info */
13091             while( *++sep >= '0' && *sep <= '9'); // strip depth
13092             if(time >= 0)
13093             while( *++sep >= '0' && *sep <= '9'); // strip time
13094             if(sec >= 0)
13095             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13096             if(deci >= 0)
13097             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13098             while(*sep == ' ') sep++;
13099         }
13100
13101         if( depth <= 0 ) {
13102             return text;
13103         }
13104
13105         if( time < 0 ) {
13106             time = -1;
13107         }
13108
13109         pvInfoList[index-1].depth = depth;
13110         pvInfoList[index-1].score = score;
13111         pvInfoList[index-1].time  = 10*time; // centi-sec
13112         if(*sep == '}') *sep = 0; else *--sep = '{';
13113     }
13114     return sep;
13115 }
13116
13117 void
13118 SendToProgram(message, cps)
13119      char *message;
13120      ChessProgramState *cps;
13121 {
13122     int count, outCount, error;
13123     char buf[MSG_SIZ];
13124
13125     if (cps->pr == NULL) return;
13126     Attention(cps);
13127     
13128     if (appData.debugMode) {
13129         TimeMark now;
13130         GetTimeMark(&now);
13131         fprintf(debugFP, "%ld >%-6s: %s", 
13132                 SubtractTimeMarks(&now, &programStartTime),
13133                 cps->which, message);
13134     }
13135     
13136     count = strlen(message);
13137     outCount = OutputToProcess(cps->pr, message, count, &error);
13138     if (outCount < count && !exiting 
13139                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13140         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13141         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13142             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13143                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13144                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13145             } else {
13146                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13147             }
13148             gameInfo.resultDetails = StrSave(buf);
13149         }
13150         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13151     }
13152 }
13153
13154 void
13155 ReceiveFromProgram(isr, closure, message, count, error)
13156      InputSourceRef isr;
13157      VOIDSTAR closure;
13158      char *message;
13159      int count;
13160      int error;
13161 {
13162     char *end_str;
13163     char buf[MSG_SIZ];
13164     ChessProgramState *cps = (ChessProgramState *)closure;
13165
13166     if (isr != cps->isr) return; /* Killed intentionally */
13167     if (count <= 0) {
13168         if (count == 0) {
13169             sprintf(buf,
13170                     _("Error: %s chess program (%s) exited unexpectedly"),
13171                     cps->which, cps->program);
13172         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13173                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13174                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13175                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13176                 } else {
13177                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13178                 }
13179                 gameInfo.resultDetails = StrSave(buf);
13180             }
13181             RemoveInputSource(cps->isr);
13182             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13183         } else {
13184             sprintf(buf,
13185                     _("Error reading from %s chess program (%s)"),
13186                     cps->which, cps->program);
13187             RemoveInputSource(cps->isr);
13188
13189             /* [AS] Program is misbehaving badly... kill it */
13190             if( count == -2 ) {
13191                 DestroyChildProcess( cps->pr, 9 );
13192                 cps->pr = NoProc;
13193             }
13194
13195             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13196         }
13197         return;
13198     }
13199     
13200     if ((end_str = strchr(message, '\r')) != NULL)
13201       *end_str = NULLCHAR;
13202     if ((end_str = strchr(message, '\n')) != NULL)
13203       *end_str = NULLCHAR;
13204     
13205     if (appData.debugMode) {
13206         TimeMark now; int print = 1;
13207         char *quote = ""; char c; int i;
13208
13209         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13210                 char start = message[0];
13211                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13212                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13213                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13214                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13215                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13216                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13217                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13218                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13219                         { quote = "# "; print = (appData.engineComments == 2); }
13220                 message[0] = start; // restore original message
13221         }
13222         if(print) {
13223                 GetTimeMark(&now);
13224                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13225                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13226                         quote,
13227                         message);
13228         }
13229     }
13230
13231     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13232     if (appData.icsEngineAnalyze) {
13233         if (strstr(message, "whisper") != NULL ||
13234              strstr(message, "kibitz") != NULL || 
13235             strstr(message, "tellics") != NULL) return;
13236     }
13237
13238     HandleMachineMove(message, cps);
13239 }
13240
13241
13242 void
13243 SendTimeControl(cps, mps, tc, inc, sd, st)
13244      ChessProgramState *cps;
13245      int mps, inc, sd, st;
13246      long tc;
13247 {
13248     char buf[MSG_SIZ];
13249     int seconds;
13250
13251     if( timeControl_2 > 0 ) {
13252         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13253             tc = timeControl_2;
13254         }
13255     }
13256     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13257     inc /= cps->timeOdds;
13258     st  /= cps->timeOdds;
13259
13260     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13261
13262     if (st > 0) {
13263       /* Set exact time per move, normally using st command */
13264       if (cps->stKludge) {
13265         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13266         seconds = st % 60;
13267         if (seconds == 0) {
13268           sprintf(buf, "level 1 %d\n", st/60);
13269         } else {
13270           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13271         }
13272       } else {
13273         sprintf(buf, "st %d\n", st);
13274       }
13275     } else {
13276       /* Set conventional or incremental time control, using level command */
13277       if (seconds == 0) {
13278         /* Note old gnuchess bug -- minutes:seconds used to not work.
13279            Fixed in later versions, but still avoid :seconds
13280            when seconds is 0. */
13281         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13282       } else {
13283         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13284                 seconds, inc/1000);
13285       }
13286     }
13287     SendToProgram(buf, cps);
13288
13289     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13290     /* Orthogonally, limit search to given depth */
13291     if (sd > 0) {
13292       if (cps->sdKludge) {
13293         sprintf(buf, "depth\n%d\n", sd);
13294       } else {
13295         sprintf(buf, "sd %d\n", sd);
13296       }
13297       SendToProgram(buf, cps);
13298     }
13299
13300     if(cps->nps > 0) { /* [HGM] nps */
13301         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13302         else {
13303                 sprintf(buf, "nps %d\n", cps->nps);
13304               SendToProgram(buf, cps);
13305         }
13306     }
13307 }
13308
13309 ChessProgramState *WhitePlayer()
13310 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13311 {
13312     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13313        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13314         return &second;
13315     return &first;
13316 }
13317
13318 void
13319 SendTimeRemaining(cps, machineWhite)
13320      ChessProgramState *cps;
13321      int /*boolean*/ machineWhite;
13322 {
13323     char message[MSG_SIZ];
13324     long time, otime;
13325
13326     /* Note: this routine must be called when the clocks are stopped
13327        or when they have *just* been set or switched; otherwise
13328        it will be off by the time since the current tick started.
13329     */
13330     if (machineWhite) {
13331         time = whiteTimeRemaining / 10;
13332         otime = blackTimeRemaining / 10;
13333     } else {
13334         time = blackTimeRemaining / 10;
13335         otime = whiteTimeRemaining / 10;
13336     }
13337     /* [HGM] translate opponent's time by time-odds factor */
13338     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13339     if (appData.debugMode) {
13340         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13341     }
13342
13343     if (time <= 0) time = 1;
13344     if (otime <= 0) otime = 1;
13345     
13346     sprintf(message, "time %ld\n", time);
13347     SendToProgram(message, cps);
13348
13349     sprintf(message, "otim %ld\n", otime);
13350     SendToProgram(message, cps);
13351 }
13352
13353 int
13354 BoolFeature(p, name, loc, cps)
13355      char **p;
13356      char *name;
13357      int *loc;
13358      ChessProgramState *cps;
13359 {
13360   char buf[MSG_SIZ];
13361   int len = strlen(name);
13362   int val;
13363   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13364     (*p) += len + 1;
13365     sscanf(*p, "%d", &val);
13366     *loc = (val != 0);
13367     while (**p && **p != ' ') (*p)++;
13368     sprintf(buf, "accepted %s\n", name);
13369     SendToProgram(buf, cps);
13370     return TRUE;
13371   }
13372   return FALSE;
13373 }
13374
13375 int
13376 IntFeature(p, name, loc, cps)
13377      char **p;
13378      char *name;
13379      int *loc;
13380      ChessProgramState *cps;
13381 {
13382   char buf[MSG_SIZ];
13383   int len = strlen(name);
13384   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13385     (*p) += len + 1;
13386     sscanf(*p, "%d", loc);
13387     while (**p && **p != ' ') (*p)++;
13388     sprintf(buf, "accepted %s\n", name);
13389     SendToProgram(buf, cps);
13390     return TRUE;
13391   }
13392   return FALSE;
13393 }
13394
13395 int
13396 StringFeature(p, name, loc, cps)
13397      char **p;
13398      char *name;
13399      char loc[];
13400      ChessProgramState *cps;
13401 {
13402   char buf[MSG_SIZ];
13403   int len = strlen(name);
13404   if (strncmp((*p), name, len) == 0
13405       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13406     (*p) += len + 2;
13407     sscanf(*p, "%[^\"]", loc);
13408     while (**p && **p != '\"') (*p)++;
13409     if (**p == '\"') (*p)++;
13410     sprintf(buf, "accepted %s\n", name);
13411     SendToProgram(buf, cps);
13412     return TRUE;
13413   }
13414   return FALSE;
13415 }
13416
13417 int 
13418 ParseOption(Option *opt, ChessProgramState *cps)
13419 // [HGM] options: process the string that defines an engine option, and determine
13420 // name, type, default value, and allowed value range
13421 {
13422         char *p, *q, buf[MSG_SIZ];
13423         int n, min = (-1)<<31, max = 1<<31, def;
13424
13425         if(p = strstr(opt->name, " -spin ")) {
13426             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13427             if(max < min) max = min; // enforce consistency
13428             if(def < min) def = min;
13429             if(def > max) def = max;
13430             opt->value = def;
13431             opt->min = min;
13432             opt->max = max;
13433             opt->type = Spin;
13434         } else if((p = strstr(opt->name, " -slider "))) {
13435             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13436             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13437             if(max < min) max = min; // enforce consistency
13438             if(def < min) def = min;
13439             if(def > max) def = max;
13440             opt->value = def;
13441             opt->min = min;
13442             opt->max = max;
13443             opt->type = Spin; // Slider;
13444         } else if((p = strstr(opt->name, " -string "))) {
13445             opt->textValue = p+9;
13446             opt->type = TextBox;
13447         } else if((p = strstr(opt->name, " -file "))) {
13448             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13449             opt->textValue = p+7;
13450             opt->type = TextBox; // FileName;
13451         } else if((p = strstr(opt->name, " -path "))) {
13452             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13453             opt->textValue = p+7;
13454             opt->type = TextBox; // PathName;
13455         } else if(p = strstr(opt->name, " -check ")) {
13456             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13457             opt->value = (def != 0);
13458             opt->type = CheckBox;
13459         } else if(p = strstr(opt->name, " -combo ")) {
13460             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13461             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13462             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13463             opt->value = n = 0;
13464             while(q = StrStr(q, " /// ")) {
13465                 n++; *q = 0;    // count choices, and null-terminate each of them
13466                 q += 5;
13467                 if(*q == '*') { // remember default, which is marked with * prefix
13468                     q++;
13469                     opt->value = n;
13470                 }
13471                 cps->comboList[cps->comboCnt++] = q;
13472             }
13473             cps->comboList[cps->comboCnt++] = NULL;
13474             opt->max = n + 1;
13475             opt->type = ComboBox;
13476         } else if(p = strstr(opt->name, " -button")) {
13477             opt->type = Button;
13478         } else if(p = strstr(opt->name, " -save")) {
13479             opt->type = SaveButton;
13480         } else return FALSE;
13481         *p = 0; // terminate option name
13482         // now look if the command-line options define a setting for this engine option.
13483         if(cps->optionSettings && cps->optionSettings[0])
13484             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13485         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13486                 sprintf(buf, "option %s", p);
13487                 if(p = strstr(buf, ",")) *p = 0;
13488                 strcat(buf, "\n");
13489                 SendToProgram(buf, cps);
13490         }
13491         return TRUE;
13492 }
13493
13494 void
13495 FeatureDone(cps, val)
13496      ChessProgramState* cps;
13497      int val;
13498 {
13499   DelayedEventCallback cb = GetDelayedEvent();
13500   if ((cb == InitBackEnd3 && cps == &first) ||
13501       (cb == TwoMachinesEventIfReady && cps == &second)) {
13502     CancelDelayedEvent();
13503     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13504   }
13505   cps->initDone = val;
13506 }
13507
13508 /* Parse feature command from engine */
13509 void
13510 ParseFeatures(args, cps)
13511      char* args;
13512      ChessProgramState *cps;  
13513 {
13514   char *p = args;
13515   char *q;
13516   int val;
13517   char buf[MSG_SIZ];
13518
13519   for (;;) {
13520     while (*p == ' ') p++;
13521     if (*p == NULLCHAR) return;
13522
13523     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13524     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13525     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13526     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13527     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13528     if (BoolFeature(&p, "reuse", &val, cps)) {
13529       /* Engine can disable reuse, but can't enable it if user said no */
13530       if (!val) cps->reuse = FALSE;
13531       continue;
13532     }
13533     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13534     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13535       if (gameMode == TwoMachinesPlay) {
13536         DisplayTwoMachinesTitle();
13537       } else {
13538         DisplayTitle("");
13539       }
13540       continue;
13541     }
13542     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13543     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13544     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13545     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13546     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13547     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13548     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13549     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13550     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13551     if (IntFeature(&p, "done", &val, cps)) {
13552       FeatureDone(cps, val);
13553       continue;
13554     }
13555     /* Added by Tord: */
13556     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13557     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13558     /* End of additions by Tord */
13559
13560     /* [HGM] added features: */
13561     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13562     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13563     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13564     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13565     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13566     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13567     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13568         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13569             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13570             SendToProgram(buf, cps);
13571             continue;
13572         }
13573         if(cps->nrOptions >= MAX_OPTIONS) {
13574             cps->nrOptions--;
13575             sprintf(buf, "%s engine has too many options\n", cps->which);
13576             DisplayError(buf, 0);
13577         }
13578         continue;
13579     }
13580     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13581     /* End of additions by HGM */
13582
13583     /* unknown feature: complain and skip */
13584     q = p;
13585     while (*q && *q != '=') q++;
13586     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13587     SendToProgram(buf, cps);
13588     p = q;
13589     if (*p == '=') {
13590       p++;
13591       if (*p == '\"') {
13592         p++;
13593         while (*p && *p != '\"') p++;
13594         if (*p == '\"') p++;
13595       } else {
13596         while (*p && *p != ' ') p++;
13597       }
13598     }
13599   }
13600
13601 }
13602
13603 void
13604 PeriodicUpdatesEvent(newState)
13605      int newState;
13606 {
13607     if (newState == appData.periodicUpdates)
13608       return;
13609
13610     appData.periodicUpdates=newState;
13611
13612     /* Display type changes, so update it now */
13613 //    DisplayAnalysis();
13614
13615     /* Get the ball rolling again... */
13616     if (newState) {
13617         AnalysisPeriodicEvent(1);
13618         StartAnalysisClock();
13619     }
13620 }
13621
13622 void
13623 PonderNextMoveEvent(newState)
13624      int newState;
13625 {
13626     if (newState == appData.ponderNextMove) return;
13627     if (gameMode == EditPosition) EditPositionDone(TRUE);
13628     if (newState) {
13629         SendToProgram("hard\n", &first);
13630         if (gameMode == TwoMachinesPlay) {
13631             SendToProgram("hard\n", &second);
13632         }
13633     } else {
13634         SendToProgram("easy\n", &first);
13635         thinkOutput[0] = NULLCHAR;
13636         if (gameMode == TwoMachinesPlay) {
13637             SendToProgram("easy\n", &second);
13638         }
13639     }
13640     appData.ponderNextMove = newState;
13641 }
13642
13643 void
13644 NewSettingEvent(option, command, value)
13645      char *command;
13646      int option, value;
13647 {
13648     char buf[MSG_SIZ];
13649
13650     if (gameMode == EditPosition) EditPositionDone(TRUE);
13651     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13652     SendToProgram(buf, &first);
13653     if (gameMode == TwoMachinesPlay) {
13654         SendToProgram(buf, &second);
13655     }
13656 }
13657
13658 void
13659 ShowThinkingEvent()
13660 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13661 {
13662     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13663     int newState = appData.showThinking
13664         // [HGM] thinking: other features now need thinking output as well
13665         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13666     
13667     if (oldState == newState) return;
13668     oldState = newState;
13669     if (gameMode == EditPosition) EditPositionDone(TRUE);
13670     if (oldState) {
13671         SendToProgram("post\n", &first);
13672         if (gameMode == TwoMachinesPlay) {
13673             SendToProgram("post\n", &second);
13674         }
13675     } else {
13676         SendToProgram("nopost\n", &first);
13677         thinkOutput[0] = NULLCHAR;
13678         if (gameMode == TwoMachinesPlay) {
13679             SendToProgram("nopost\n", &second);
13680         }
13681     }
13682 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13683 }
13684
13685 void
13686 AskQuestionEvent(title, question, replyPrefix, which)
13687      char *title; char *question; char *replyPrefix; char *which;
13688 {
13689   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13690   if (pr == NoProc) return;
13691   AskQuestion(title, question, replyPrefix, pr);
13692 }
13693
13694 void
13695 DisplayMove(moveNumber)
13696      int moveNumber;
13697 {
13698     char message[MSG_SIZ];
13699     char res[MSG_SIZ];
13700     char cpThinkOutput[MSG_SIZ];
13701
13702     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13703     
13704     if (moveNumber == forwardMostMove - 1 || 
13705         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13706
13707         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13708
13709         if (strchr(cpThinkOutput, '\n')) {
13710             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13711         }
13712     } else {
13713         *cpThinkOutput = NULLCHAR;
13714     }
13715
13716     /* [AS] Hide thinking from human user */
13717     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13718         *cpThinkOutput = NULLCHAR;
13719         if( thinkOutput[0] != NULLCHAR ) {
13720             int i;
13721
13722             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13723                 cpThinkOutput[i] = '.';
13724             }
13725             cpThinkOutput[i] = NULLCHAR;
13726             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13727         }
13728     }
13729
13730     if (moveNumber == forwardMostMove - 1 &&
13731         gameInfo.resultDetails != NULL) {
13732         if (gameInfo.resultDetails[0] == NULLCHAR) {
13733             sprintf(res, " %s", PGNResult(gameInfo.result));
13734         } else {
13735             sprintf(res, " {%s} %s",
13736                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13737         }
13738     } else {
13739         res[0] = NULLCHAR;
13740     }
13741
13742     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13743         DisplayMessage(res, cpThinkOutput);
13744     } else {
13745         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13746                 WhiteOnMove(moveNumber) ? " " : ".. ",
13747                 parseList[moveNumber], res);
13748         DisplayMessage(message, cpThinkOutput);
13749     }
13750 }
13751
13752 void
13753 DisplayComment(moveNumber, text)
13754      int moveNumber;
13755      char *text;
13756 {
13757     char title[MSG_SIZ];
13758     char buf[8000]; // comment can be long!
13759     int score, depth;
13760     
13761     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13762       strcpy(title, "Comment");
13763     } else {
13764       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13765               WhiteOnMove(moveNumber) ? " " : ".. ",
13766               parseList[moveNumber]);
13767     }
13768     // [HGM] PV info: display PV info together with (or as) comment
13769     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13770       if(text == NULL) text = "";                                           
13771       score = pvInfoList[moveNumber].score;
13772       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13773               depth, (pvInfoList[moveNumber].time+50)/100, text);
13774       text = buf;
13775     }
13776     if (text != NULL && (appData.autoDisplayComment || commentUp))
13777         CommentPopUp(title, text);
13778 }
13779
13780 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13781  * might be busy thinking or pondering.  It can be omitted if your
13782  * gnuchess is configured to stop thinking immediately on any user
13783  * input.  However, that gnuchess feature depends on the FIONREAD
13784  * ioctl, which does not work properly on some flavors of Unix.
13785  */
13786 void
13787 Attention(cps)
13788      ChessProgramState *cps;
13789 {
13790 #if ATTENTION
13791     if (!cps->useSigint) return;
13792     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13793     switch (gameMode) {
13794       case MachinePlaysWhite:
13795       case MachinePlaysBlack:
13796       case TwoMachinesPlay:
13797       case IcsPlayingWhite:
13798       case IcsPlayingBlack:
13799       case AnalyzeMode:
13800       case AnalyzeFile:
13801         /* Skip if we know it isn't thinking */
13802         if (!cps->maybeThinking) return;
13803         if (appData.debugMode)
13804           fprintf(debugFP, "Interrupting %s\n", cps->which);
13805         InterruptChildProcess(cps->pr);
13806         cps->maybeThinking = FALSE;
13807         break;
13808       default:
13809         break;
13810     }
13811 #endif /*ATTENTION*/
13812 }
13813
13814 int
13815 CheckFlags()
13816 {
13817     if (whiteTimeRemaining <= 0) {
13818         if (!whiteFlag) {
13819             whiteFlag = TRUE;
13820             if (appData.icsActive) {
13821                 if (appData.autoCallFlag &&
13822                     gameMode == IcsPlayingBlack && !blackFlag) {
13823                   SendToICS(ics_prefix);
13824                   SendToICS("flag\n");
13825                 }
13826             } else {
13827                 if (blackFlag) {
13828                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13829                 } else {
13830                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13831                     if (appData.autoCallFlag) {
13832                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13833                         return TRUE;
13834                     }
13835                 }
13836             }
13837         }
13838     }
13839     if (blackTimeRemaining <= 0) {
13840         if (!blackFlag) {
13841             blackFlag = TRUE;
13842             if (appData.icsActive) {
13843                 if (appData.autoCallFlag &&
13844                     gameMode == IcsPlayingWhite && !whiteFlag) {
13845                   SendToICS(ics_prefix);
13846                   SendToICS("flag\n");
13847                 }
13848             } else {
13849                 if (whiteFlag) {
13850                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13851                 } else {
13852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13853                     if (appData.autoCallFlag) {
13854                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13855                         return TRUE;
13856                     }
13857                 }
13858             }
13859         }
13860     }
13861     return FALSE;
13862 }
13863
13864 void
13865 CheckTimeControl()
13866 {
13867     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13868         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13869
13870     /*
13871      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13872      */
13873     if ( !WhiteOnMove(forwardMostMove) )
13874         /* White made time control */
13875         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13876         /* [HGM] time odds: correct new time quota for time odds! */
13877                                             / WhitePlayer()->timeOdds;
13878       else
13879         /* Black made time control */
13880         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13881                                             / WhitePlayer()->other->timeOdds;
13882 }
13883
13884 void
13885 DisplayBothClocks()
13886 {
13887     int wom = gameMode == EditPosition ?
13888       !blackPlaysFirst : WhiteOnMove(currentMove);
13889     DisplayWhiteClock(whiteTimeRemaining, wom);
13890     DisplayBlackClock(blackTimeRemaining, !wom);
13891 }
13892
13893
13894 /* Timekeeping seems to be a portability nightmare.  I think everyone
13895    has ftime(), but I'm really not sure, so I'm including some ifdefs
13896    to use other calls if you don't.  Clocks will be less accurate if
13897    you have neither ftime nor gettimeofday.
13898 */
13899
13900 /* VS 2008 requires the #include outside of the function */
13901 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13902 #include <sys/timeb.h>
13903 #endif
13904
13905 /* Get the current time as a TimeMark */
13906 void
13907 GetTimeMark(tm)
13908      TimeMark *tm;
13909 {
13910 #if HAVE_GETTIMEOFDAY
13911
13912     struct timeval timeVal;
13913     struct timezone timeZone;
13914
13915     gettimeofday(&timeVal, &timeZone);
13916     tm->sec = (long) timeVal.tv_sec; 
13917     tm->ms = (int) (timeVal.tv_usec / 1000L);
13918
13919 #else /*!HAVE_GETTIMEOFDAY*/
13920 #if HAVE_FTIME
13921
13922 // include <sys/timeb.h> / moved to just above start of function
13923     struct timeb timeB;
13924
13925     ftime(&timeB);
13926     tm->sec = (long) timeB.time;
13927     tm->ms = (int) timeB.millitm;
13928
13929 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13930     tm->sec = (long) time(NULL);
13931     tm->ms = 0;
13932 #endif
13933 #endif
13934 }
13935
13936 /* Return the difference in milliseconds between two
13937    time marks.  We assume the difference will fit in a long!
13938 */
13939 long
13940 SubtractTimeMarks(tm2, tm1)
13941      TimeMark *tm2, *tm1;
13942 {
13943     return 1000L*(tm2->sec - tm1->sec) +
13944            (long) (tm2->ms - tm1->ms);
13945 }
13946
13947
13948 /*
13949  * Code to manage the game clocks.
13950  *
13951  * In tournament play, black starts the clock and then white makes a move.
13952  * We give the human user a slight advantage if he is playing white---the
13953  * clocks don't run until he makes his first move, so it takes zero time.
13954  * Also, we don't account for network lag, so we could get out of sync
13955  * with GNU Chess's clock -- but then, referees are always right.  
13956  */
13957
13958 static TimeMark tickStartTM;
13959 static long intendedTickLength;
13960
13961 long
13962 NextTickLength(timeRemaining)
13963      long timeRemaining;
13964 {
13965     long nominalTickLength, nextTickLength;
13966
13967     if (timeRemaining > 0L && timeRemaining <= 10000L)
13968       nominalTickLength = 100L;
13969     else
13970       nominalTickLength = 1000L;
13971     nextTickLength = timeRemaining % nominalTickLength;
13972     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13973
13974     return nextTickLength;
13975 }
13976
13977 /* Adjust clock one minute up or down */
13978 void
13979 AdjustClock(Boolean which, int dir)
13980 {
13981     if(which) blackTimeRemaining += 60000*dir;
13982     else      whiteTimeRemaining += 60000*dir;
13983     DisplayBothClocks();
13984 }
13985
13986 /* Stop clocks and reset to a fresh time control */
13987 void
13988 ResetClocks() 
13989 {
13990     (void) StopClockTimer();
13991     if (appData.icsActive) {
13992         whiteTimeRemaining = blackTimeRemaining = 0;
13993     } else if (searchTime) {
13994         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13995         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13996     } else { /* [HGM] correct new time quote for time odds */
13997         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13998         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13999     }
14000     if (whiteFlag || blackFlag) {
14001         DisplayTitle("");
14002         whiteFlag = blackFlag = FALSE;
14003     }
14004     DisplayBothClocks();
14005 }
14006
14007 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14008
14009 /* Decrement running clock by amount of time that has passed */
14010 void
14011 DecrementClocks()
14012 {
14013     long timeRemaining;
14014     long lastTickLength, fudge;
14015     TimeMark now;
14016
14017     if (!appData.clockMode) return;
14018     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14019         
14020     GetTimeMark(&now);
14021
14022     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14023
14024     /* Fudge if we woke up a little too soon */
14025     fudge = intendedTickLength - lastTickLength;
14026     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14027
14028     if (WhiteOnMove(forwardMostMove)) {
14029         if(whiteNPS >= 0) lastTickLength = 0;
14030         timeRemaining = whiteTimeRemaining -= lastTickLength;
14031         DisplayWhiteClock(whiteTimeRemaining - fudge,
14032                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14033     } else {
14034         if(blackNPS >= 0) lastTickLength = 0;
14035         timeRemaining = blackTimeRemaining -= lastTickLength;
14036         DisplayBlackClock(blackTimeRemaining - fudge,
14037                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14038     }
14039
14040     if (CheckFlags()) return;
14041         
14042     tickStartTM = now;
14043     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14044     StartClockTimer(intendedTickLength);
14045
14046     /* if the time remaining has fallen below the alarm threshold, sound the
14047      * alarm. if the alarm has sounded and (due to a takeback or time control
14048      * with increment) the time remaining has increased to a level above the
14049      * threshold, reset the alarm so it can sound again. 
14050      */
14051     
14052     if (appData.icsActive && appData.icsAlarm) {
14053
14054         /* make sure we are dealing with the user's clock */
14055         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14056                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14057            )) return;
14058
14059         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14060             alarmSounded = FALSE;
14061         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14062             PlayAlarmSound();
14063             alarmSounded = TRUE;
14064         }
14065     }
14066 }
14067
14068
14069 /* A player has just moved, so stop the previously running
14070    clock and (if in clock mode) start the other one.
14071    We redisplay both clocks in case we're in ICS mode, because
14072    ICS gives us an update to both clocks after every move.
14073    Note that this routine is called *after* forwardMostMove
14074    is updated, so the last fractional tick must be subtracted
14075    from the color that is *not* on move now.
14076 */
14077 void
14078 SwitchClocks(int newMoveNr)
14079 {
14080     long lastTickLength;
14081     TimeMark now;
14082     int flagged = FALSE;
14083
14084     GetTimeMark(&now);
14085
14086     if (StopClockTimer() && appData.clockMode) {
14087         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14088         if (!WhiteOnMove(forwardMostMove)) {
14089             if(blackNPS >= 0) lastTickLength = 0;
14090             blackTimeRemaining -= lastTickLength;
14091            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14092 //         if(pvInfoList[forwardMostMove-1].time == -1)
14093                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14094                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14095         } else {
14096            if(whiteNPS >= 0) lastTickLength = 0;
14097            whiteTimeRemaining -= lastTickLength;
14098            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14099 //         if(pvInfoList[forwardMostMove-1].time == -1)
14100                  pvInfoList[forwardMostMove-1].time = 
14101                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14102         }
14103         flagged = CheckFlags();
14104     }
14105     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14106     CheckTimeControl();
14107
14108     if (flagged || !appData.clockMode) return;
14109
14110     switch (gameMode) {
14111       case MachinePlaysBlack:
14112       case MachinePlaysWhite:
14113       case BeginningOfGame:
14114         if (pausing) return;
14115         break;
14116
14117       case EditGame:
14118       case PlayFromGameFile:
14119       case IcsExamining:
14120         return;
14121
14122       default:
14123         break;
14124     }
14125
14126     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14127         if(WhiteOnMove(forwardMostMove))
14128              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14129         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14130     }
14131
14132     tickStartTM = now;
14133     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14134       whiteTimeRemaining : blackTimeRemaining);
14135     StartClockTimer(intendedTickLength);
14136 }
14137         
14138
14139 /* Stop both clocks */
14140 void
14141 StopClocks()
14142 {       
14143     long lastTickLength;
14144     TimeMark now;
14145
14146     if (!StopClockTimer()) return;
14147     if (!appData.clockMode) return;
14148
14149     GetTimeMark(&now);
14150
14151     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14152     if (WhiteOnMove(forwardMostMove)) {
14153         if(whiteNPS >= 0) lastTickLength = 0;
14154         whiteTimeRemaining -= lastTickLength;
14155         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14156     } else {
14157         if(blackNPS >= 0) lastTickLength = 0;
14158         blackTimeRemaining -= lastTickLength;
14159         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14160     }
14161     CheckFlags();
14162 }
14163         
14164 /* Start clock of player on move.  Time may have been reset, so
14165    if clock is already running, stop and restart it. */
14166 void
14167 StartClocks()
14168 {
14169     (void) StopClockTimer(); /* in case it was running already */
14170     DisplayBothClocks();
14171     if (CheckFlags()) return;
14172
14173     if (!appData.clockMode) return;
14174     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14175
14176     GetTimeMark(&tickStartTM);
14177     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14178       whiteTimeRemaining : blackTimeRemaining);
14179
14180    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14181     whiteNPS = blackNPS = -1; 
14182     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14183        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14184         whiteNPS = first.nps;
14185     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14186        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14187         blackNPS = first.nps;
14188     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14189         whiteNPS = second.nps;
14190     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14191         blackNPS = second.nps;
14192     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14193
14194     StartClockTimer(intendedTickLength);
14195 }
14196
14197 char *
14198 TimeString(ms)
14199      long ms;
14200 {
14201     long second, minute, hour, day;
14202     char *sign = "";
14203     static char buf[32];
14204     
14205     if (ms > 0 && ms <= 9900) {
14206       /* convert milliseconds to tenths, rounding up */
14207       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14208
14209       sprintf(buf, " %03.1f ", tenths/10.0);
14210       return buf;
14211     }
14212
14213     /* convert milliseconds to seconds, rounding up */
14214     /* use floating point to avoid strangeness of integer division
14215        with negative dividends on many machines */
14216     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14217
14218     if (second < 0) {
14219         sign = "-";
14220         second = -second;
14221     }
14222     
14223     day = second / (60 * 60 * 24);
14224     second = second % (60 * 60 * 24);
14225     hour = second / (60 * 60);
14226     second = second % (60 * 60);
14227     minute = second / 60;
14228     second = second % 60;
14229     
14230     if (day > 0)
14231       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14232               sign, day, hour, minute, second);
14233     else if (hour > 0)
14234       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14235     else
14236       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14237     
14238     return buf;
14239 }
14240
14241
14242 /*
14243  * This is necessary because some C libraries aren't ANSI C compliant yet.
14244  */
14245 char *
14246 StrStr(string, match)
14247      char *string, *match;
14248 {
14249     int i, length;
14250     
14251     length = strlen(match);
14252     
14253     for (i = strlen(string) - length; i >= 0; i--, string++)
14254       if (!strncmp(match, string, length))
14255         return string;
14256     
14257     return NULL;
14258 }
14259
14260 char *
14261 StrCaseStr(string, match)
14262      char *string, *match;
14263 {
14264     int i, j, length;
14265     
14266     length = strlen(match);
14267     
14268     for (i = strlen(string) - length; i >= 0; i--, string++) {
14269         for (j = 0; j < length; j++) {
14270             if (ToLower(match[j]) != ToLower(string[j]))
14271               break;
14272         }
14273         if (j == length) return string;
14274     }
14275
14276     return NULL;
14277 }
14278
14279 #ifndef _amigados
14280 int
14281 StrCaseCmp(s1, s2)
14282      char *s1, *s2;
14283 {
14284     char c1, c2;
14285     
14286     for (;;) {
14287         c1 = ToLower(*s1++);
14288         c2 = ToLower(*s2++);
14289         if (c1 > c2) return 1;
14290         if (c1 < c2) return -1;
14291         if (c1 == NULLCHAR) return 0;
14292     }
14293 }
14294
14295
14296 int
14297 ToLower(c)
14298      int c;
14299 {
14300     return isupper(c) ? tolower(c) : c;
14301 }
14302
14303
14304 int
14305 ToUpper(c)
14306      int c;
14307 {
14308     return islower(c) ? toupper(c) : c;
14309 }
14310 #endif /* !_amigados    */
14311
14312 char *
14313 StrSave(s)
14314      char *s;
14315 {
14316     char *ret;
14317
14318     if ((ret = (char *) malloc(strlen(s) + 1))) {
14319         strcpy(ret, s);
14320     }
14321     return ret;
14322 }
14323
14324 char *
14325 StrSavePtr(s, savePtr)
14326      char *s, **savePtr;
14327 {
14328     if (*savePtr) {
14329         free(*savePtr);
14330     }
14331     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14332         strcpy(*savePtr, s);
14333     }
14334     return(*savePtr);
14335 }
14336
14337 char *
14338 PGNDate()
14339 {
14340     time_t clock;
14341     struct tm *tm;
14342     char buf[MSG_SIZ];
14343
14344     clock = time((time_t *)NULL);
14345     tm = localtime(&clock);
14346     sprintf(buf, "%04d.%02d.%02d",
14347             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14348     return StrSave(buf);
14349 }
14350
14351
14352 char *
14353 PositionToFEN(move, overrideCastling)
14354      int move;
14355      char *overrideCastling;
14356 {
14357     int i, j, fromX, fromY, toX, toY;
14358     int whiteToPlay;
14359     char buf[128];
14360     char *p, *q;
14361     int emptycount;
14362     ChessSquare piece;
14363
14364     whiteToPlay = (gameMode == EditPosition) ?
14365       !blackPlaysFirst : (move % 2 == 0);
14366     p = buf;
14367
14368     /* Piece placement data */
14369     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14370         emptycount = 0;
14371         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14372             if (boards[move][i][j] == EmptySquare) {
14373                 emptycount++;
14374             } else { ChessSquare piece = boards[move][i][j];
14375                 if (emptycount > 0) {
14376                     if(emptycount<10) /* [HGM] can be >= 10 */
14377                         *p++ = '0' + emptycount;
14378                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14379                     emptycount = 0;
14380                 }
14381                 if(PieceToChar(piece) == '+') {
14382                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14383                     *p++ = '+';
14384                     piece = (ChessSquare)(DEMOTED piece);
14385                 } 
14386                 *p++ = PieceToChar(piece);
14387                 if(p[-1] == '~') {
14388                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14389                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14390                     *p++ = '~';
14391                 }
14392             }
14393         }
14394         if (emptycount > 0) {
14395             if(emptycount<10) /* [HGM] can be >= 10 */
14396                 *p++ = '0' + emptycount;
14397             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14398             emptycount = 0;
14399         }
14400         *p++ = '/';
14401     }
14402     *(p - 1) = ' ';
14403
14404     /* [HGM] print Crazyhouse or Shogi holdings */
14405     if( gameInfo.holdingsWidth ) {
14406         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14407         q = p;
14408         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14409             piece = boards[move][i][BOARD_WIDTH-1];
14410             if( piece != EmptySquare )
14411               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14412                   *p++ = PieceToChar(piece);
14413         }
14414         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14415             piece = boards[move][BOARD_HEIGHT-i-1][0];
14416             if( piece != EmptySquare )
14417               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14418                   *p++ = PieceToChar(piece);
14419         }
14420
14421         if( q == p ) *p++ = '-';
14422         *p++ = ']';
14423         *p++ = ' ';
14424     }
14425
14426     /* Active color */
14427     *p++ = whiteToPlay ? 'w' : 'b';
14428     *p++ = ' ';
14429
14430   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14431     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14432   } else {
14433   if(nrCastlingRights) {
14434      q = p;
14435      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14436        /* [HGM] write directly from rights */
14437            if(boards[move][CASTLING][2] != NoRights &&
14438               boards[move][CASTLING][0] != NoRights   )
14439                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14440            if(boards[move][CASTLING][2] != NoRights &&
14441               boards[move][CASTLING][1] != NoRights   )
14442                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14443            if(boards[move][CASTLING][5] != NoRights &&
14444               boards[move][CASTLING][3] != NoRights   )
14445                 *p++ = boards[move][CASTLING][3] + AAA;
14446            if(boards[move][CASTLING][5] != NoRights &&
14447               boards[move][CASTLING][4] != NoRights   )
14448                 *p++ = boards[move][CASTLING][4] + AAA;
14449      } else {
14450
14451         /* [HGM] write true castling rights */
14452         if( nrCastlingRights == 6 ) {
14453             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14454                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14455             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14456                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14457             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14458                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14459             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14460                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14461         }
14462      }
14463      if (q == p) *p++ = '-'; /* No castling rights */
14464      *p++ = ' ';
14465   }
14466
14467   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14468      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14469     /* En passant target square */
14470     if (move > backwardMostMove) {
14471         fromX = moveList[move - 1][0] - AAA;
14472         fromY = moveList[move - 1][1] - ONE;
14473         toX = moveList[move - 1][2] - AAA;
14474         toY = moveList[move - 1][3] - ONE;
14475         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14476             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14477             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14478             fromX == toX) {
14479             /* 2-square pawn move just happened */
14480             *p++ = toX + AAA;
14481             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14482         } else {
14483             *p++ = '-';
14484         }
14485     } else if(move == backwardMostMove) {
14486         // [HGM] perhaps we should always do it like this, and forget the above?
14487         if((signed char)boards[move][EP_STATUS] >= 0) {
14488             *p++ = boards[move][EP_STATUS] + AAA;
14489             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14490         } else {
14491             *p++ = '-';
14492         }
14493     } else {
14494         *p++ = '-';
14495     }
14496     *p++ = ' ';
14497   }
14498   }
14499
14500     /* [HGM] find reversible plies */
14501     {   int i = 0, j=move;
14502
14503         if (appData.debugMode) { int k;
14504             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14505             for(k=backwardMostMove; k<=forwardMostMove; k++)
14506                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14507
14508         }
14509
14510         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14511         if( j == backwardMostMove ) i += initialRulePlies;
14512         sprintf(p, "%d ", i);
14513         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14514     }
14515     /* Fullmove number */
14516     sprintf(p, "%d", (move / 2) + 1);
14517     
14518     return StrSave(buf);
14519 }
14520
14521 Boolean
14522 ParseFEN(board, blackPlaysFirst, fen)
14523     Board board;
14524      int *blackPlaysFirst;
14525      char *fen;
14526 {
14527     int i, j;
14528     char *p;
14529     int emptycount;
14530     ChessSquare piece;
14531
14532     p = fen;
14533
14534     /* [HGM] by default clear Crazyhouse holdings, if present */
14535     if(gameInfo.holdingsWidth) {
14536        for(i=0; i<BOARD_HEIGHT; i++) {
14537            board[i][0]             = EmptySquare; /* black holdings */
14538            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14539            board[i][1]             = (ChessSquare) 0; /* black counts */
14540            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14541        }
14542     }
14543
14544     /* Piece placement data */
14545     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14546         j = 0;
14547         for (;;) {
14548             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14549                 if (*p == '/') p++;
14550                 emptycount = gameInfo.boardWidth - j;
14551                 while (emptycount--)
14552                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14553                 break;
14554 #if(BOARD_FILES >= 10)
14555             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14556                 p++; emptycount=10;
14557                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14558                 while (emptycount--)
14559                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14560 #endif
14561             } else if (isdigit(*p)) {
14562                 emptycount = *p++ - '0';
14563                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14564                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14565                 while (emptycount--)
14566                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14567             } else if (*p == '+' || isalpha(*p)) {
14568                 if (j >= gameInfo.boardWidth) return FALSE;
14569                 if(*p=='+') {
14570                     piece = CharToPiece(*++p);
14571                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14572                     piece = (ChessSquare) (PROMOTED piece ); p++;
14573                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14574                 } else piece = CharToPiece(*p++);
14575
14576                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14577                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14578                     piece = (ChessSquare) (PROMOTED piece);
14579                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14580                     p++;
14581                 }
14582                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14583             } else {
14584                 return FALSE;
14585             }
14586         }
14587     }
14588     while (*p == '/' || *p == ' ') p++;
14589
14590     /* [HGM] look for Crazyhouse holdings here */
14591     while(*p==' ') p++;
14592     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14593         if(*p == '[') p++;
14594         if(*p == '-' ) *p++; /* empty holdings */ else {
14595             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14596             /* if we would allow FEN reading to set board size, we would   */
14597             /* have to add holdings and shift the board read so far here   */
14598             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14599                 *p++;
14600                 if((int) piece >= (int) BlackPawn ) {
14601                     i = (int)piece - (int)BlackPawn;
14602                     i = PieceToNumber((ChessSquare)i);
14603                     if( i >= gameInfo.holdingsSize ) return FALSE;
14604                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14605                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14606                 } else {
14607                     i = (int)piece - (int)WhitePawn;
14608                     i = PieceToNumber((ChessSquare)i);
14609                     if( i >= gameInfo.holdingsSize ) return FALSE;
14610                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14611                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14612                 }
14613             }
14614         }
14615         if(*p == ']') *p++;
14616     }
14617
14618     while(*p == ' ') p++;
14619
14620     /* Active color */
14621     switch (*p++) {
14622       case 'w':
14623         *blackPlaysFirst = FALSE;
14624         break;
14625       case 'b': 
14626         *blackPlaysFirst = TRUE;
14627         break;
14628       default:
14629         return FALSE;
14630     }
14631
14632     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14633     /* return the extra info in global variiables             */
14634
14635     /* set defaults in case FEN is incomplete */
14636     board[EP_STATUS] = EP_UNKNOWN;
14637     for(i=0; i<nrCastlingRights; i++ ) {
14638         board[CASTLING][i] =
14639             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14640     }   /* assume possible unless obviously impossible */
14641     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14642     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14643     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14644                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14645     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14646     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14647     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14648                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14649     FENrulePlies = 0;
14650
14651     while(*p==' ') p++;
14652     if(nrCastlingRights) {
14653       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14654           /* castling indicator present, so default becomes no castlings */
14655           for(i=0; i<nrCastlingRights; i++ ) {
14656                  board[CASTLING][i] = NoRights;
14657           }
14658       }
14659       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14660              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14661              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14662              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14663         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14664
14665         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14666             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14667             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14668         }
14669         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14670             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14671         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14672                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14673         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14674                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14675         switch(c) {
14676           case'K':
14677               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14678               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14679               board[CASTLING][2] = whiteKingFile;
14680               break;
14681           case'Q':
14682               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14683               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14684               board[CASTLING][2] = whiteKingFile;
14685               break;
14686           case'k':
14687               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14688               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14689               board[CASTLING][5] = blackKingFile;
14690               break;
14691           case'q':
14692               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14693               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14694               board[CASTLING][5] = blackKingFile;
14695           case '-':
14696               break;
14697           default: /* FRC castlings */
14698               if(c >= 'a') { /* black rights */
14699                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14700                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14701                   if(i == BOARD_RGHT) break;
14702                   board[CASTLING][5] = i;
14703                   c -= AAA;
14704                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14705                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14706                   if(c > i)
14707                       board[CASTLING][3] = c;
14708                   else
14709                       board[CASTLING][4] = c;
14710               } else { /* white rights */
14711                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14712                     if(board[0][i] == WhiteKing) break;
14713                   if(i == BOARD_RGHT) break;
14714                   board[CASTLING][2] = i;
14715                   c -= AAA - 'a' + 'A';
14716                   if(board[0][c] >= WhiteKing) break;
14717                   if(c > i)
14718                       board[CASTLING][0] = c;
14719                   else
14720                       board[CASTLING][1] = c;
14721               }
14722         }
14723       }
14724       for(i=0; i<nrCastlingRights; i++)
14725         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14726     if (appData.debugMode) {
14727         fprintf(debugFP, "FEN castling rights:");
14728         for(i=0; i<nrCastlingRights; i++)
14729         fprintf(debugFP, " %d", board[CASTLING][i]);
14730         fprintf(debugFP, "\n");
14731     }
14732
14733       while(*p==' ') p++;
14734     }
14735
14736     /* read e.p. field in games that know e.p. capture */
14737     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14738        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14739       if(*p=='-') {
14740         p++; board[EP_STATUS] = EP_NONE;
14741       } else {
14742          char c = *p++ - AAA;
14743
14744          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14745          if(*p >= '0' && *p <='9') *p++;
14746          board[EP_STATUS] = c;
14747       }
14748     }
14749
14750
14751     if(sscanf(p, "%d", &i) == 1) {
14752         FENrulePlies = i; /* 50-move ply counter */
14753         /* (The move number is still ignored)    */
14754     }
14755
14756     return TRUE;
14757 }
14758       
14759 void
14760 EditPositionPasteFEN(char *fen)
14761 {
14762   if (fen != NULL) {
14763     Board initial_position;
14764
14765     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14766       DisplayError(_("Bad FEN position in clipboard"), 0);
14767       return ;
14768     } else {
14769       int savedBlackPlaysFirst = blackPlaysFirst;
14770       EditPositionEvent();
14771       blackPlaysFirst = savedBlackPlaysFirst;
14772       CopyBoard(boards[0], initial_position);
14773       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14774       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14775       DisplayBothClocks();
14776       DrawPosition(FALSE, boards[currentMove]);
14777     }
14778   }
14779 }
14780
14781 static char cseq[12] = "\\   ";
14782
14783 Boolean set_cont_sequence(char *new_seq)
14784 {
14785     int len;
14786     Boolean ret;
14787
14788     // handle bad attempts to set the sequence
14789         if (!new_seq)
14790                 return 0; // acceptable error - no debug
14791
14792     len = strlen(new_seq);
14793     ret = (len > 0) && (len < sizeof(cseq));
14794     if (ret)
14795         strcpy(cseq, new_seq);
14796     else if (appData.debugMode)
14797         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14798     return ret;
14799 }
14800
14801 /*
14802     reformat a source message so words don't cross the width boundary.  internal
14803     newlines are not removed.  returns the wrapped size (no null character unless
14804     included in source message).  If dest is NULL, only calculate the size required
14805     for the dest buffer.  lp argument indicats line position upon entry, and it's
14806     passed back upon exit.
14807 */
14808 int wrap(char *dest, char *src, int count, int width, int *lp)
14809 {
14810     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14811
14812     cseq_len = strlen(cseq);
14813     old_line = line = *lp;
14814     ansi = len = clen = 0;
14815
14816     for (i=0; i < count; i++)
14817     {
14818         if (src[i] == '\033')
14819             ansi = 1;
14820
14821         // if we hit the width, back up
14822         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14823         {
14824             // store i & len in case the word is too long
14825             old_i = i, old_len = len;
14826
14827             // find the end of the last word
14828             while (i && src[i] != ' ' && src[i] != '\n')
14829             {
14830                 i--;
14831                 len--;
14832             }
14833
14834             // word too long?  restore i & len before splitting it
14835             if ((old_i-i+clen) >= width)
14836             {
14837                 i = old_i;
14838                 len = old_len;
14839             }
14840
14841             // extra space?
14842             if (i && src[i-1] == ' ')
14843                 len--;
14844
14845             if (src[i] != ' ' && src[i] != '\n')
14846             {
14847                 i--;
14848                 if (len)
14849                     len--;
14850             }
14851
14852             // now append the newline and continuation sequence
14853             if (dest)
14854                 dest[len] = '\n';
14855             len++;
14856             if (dest)
14857                 strncpy(dest+len, cseq, cseq_len);
14858             len += cseq_len;
14859             line = cseq_len;
14860             clen = cseq_len;
14861             continue;
14862         }
14863
14864         if (dest)
14865             dest[len] = src[i];
14866         len++;
14867         if (!ansi)
14868             line++;
14869         if (src[i] == '\n')
14870             line = 0;
14871         if (src[i] == 'm')
14872             ansi = 0;
14873     }
14874     if (dest && appData.debugMode)
14875     {
14876         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14877             count, width, line, len, *lp);
14878         show_bytes(debugFP, src, count);
14879         fprintf(debugFP, "\ndest: ");
14880         show_bytes(debugFP, dest, len);
14881         fprintf(debugFP, "\n");
14882     }
14883     *lp = dest ? line : old_line;
14884
14885     return len;
14886 }
14887
14888 // [HGM] vari: routines for shelving variations
14889
14890 void 
14891 PushTail(int firstMove, int lastMove)
14892 {
14893         int i, j, nrMoves = lastMove - firstMove;
14894
14895         if(appData.icsActive) { // only in local mode
14896                 forwardMostMove = currentMove; // mimic old ICS behavior
14897                 return;
14898         }
14899         if(storedGames >= MAX_VARIATIONS-1) return;
14900
14901         // push current tail of game on stack
14902         savedResult[storedGames] = gameInfo.result;
14903         savedDetails[storedGames] = gameInfo.resultDetails;
14904         gameInfo.resultDetails = NULL;
14905         savedFirst[storedGames] = firstMove;
14906         savedLast [storedGames] = lastMove;
14907         savedFramePtr[storedGames] = framePtr;
14908         framePtr -= nrMoves; // reserve space for the boards
14909         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14910             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14911             for(j=0; j<MOVE_LEN; j++)
14912                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14913             for(j=0; j<2*MOVE_LEN; j++)
14914                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14915             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14916             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14917             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14918             pvInfoList[firstMove+i-1].depth = 0;
14919             commentList[framePtr+i] = commentList[firstMove+i];
14920             commentList[firstMove+i] = NULL;
14921         }
14922
14923         storedGames++;
14924         forwardMostMove = currentMove; // truncte game so we can start variation
14925         if(storedGames == 1) GreyRevert(FALSE);
14926 }
14927
14928 Boolean
14929 PopTail(Boolean annotate)
14930 {
14931         int i, j, nrMoves;
14932         char buf[8000], moveBuf[20];
14933
14934         if(appData.icsActive) return FALSE; // only in local mode
14935         if(!storedGames) return FALSE; // sanity
14936
14937         storedGames--;
14938         ToNrEvent(savedFirst[storedGames]); // sets currentMove
14939         nrMoves = savedLast[storedGames] - currentMove;
14940         if(annotate) {
14941                 int cnt = 10;
14942                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14943                 else strcpy(buf, "(");
14944                 for(i=currentMove; i<forwardMostMove; i++) {
14945                         if(WhiteOnMove(i))
14946                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14947                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14948                         strcat(buf, moveBuf);
14949                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14950                 }
14951                 strcat(buf, ")");
14952         }
14953         for(i=1; i<nrMoves; i++) { // copy last variation back
14954             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14955             for(j=0; j<MOVE_LEN; j++)
14956                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14957             for(j=0; j<2*MOVE_LEN; j++)
14958                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14959             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14960             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14961             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14962             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14963             commentList[currentMove+i] = commentList[framePtr+i];
14964             commentList[framePtr+i] = NULL;
14965         }
14966         if(annotate) AppendComment(currentMove+1, buf, FALSE);
14967         framePtr = savedFramePtr[storedGames];
14968         gameInfo.result = savedResult[storedGames];
14969         if(gameInfo.resultDetails != NULL) {
14970             free(gameInfo.resultDetails);
14971       }
14972         gameInfo.resultDetails = savedDetails[storedGames];
14973         forwardMostMove = currentMove + nrMoves;
14974         if(storedGames == 0) GreyRevert(TRUE);
14975         return TRUE;
14976 }
14977
14978 void 
14979 CleanupTail()
14980 {       // remove all shelved variations
14981         int i;
14982         for(i=0; i<storedGames; i++) {
14983             if(savedDetails[i])
14984                 free(savedDetails[i]);
14985             savedDetails[i] = NULL;
14986         }
14987         for(i=framePtr; i<MAX_MOVES; i++) {
14988                 if(commentList[i]) free(commentList[i]);
14989                 commentList[i] = NULL;
14990         }
14991         framePtr = MAX_MOVES-1;
14992         storedGames = 0;
14993 }