Allow WB Chat Box to be dedicated to shouts and 'it'
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h" 
130  
131 #ifdef ENABLE_NLS 
132 # define _(s) gettext (s) 
133 # define N_(s) gettext_noop (s) 
134 #else 
135 # define _(s) (s) 
136 # define N_(s) s 
137 #endif 
138
139
140 /* A point in time */
141 typedef struct {
142     long sec;  /* Assuming this is >= 32 bits */
143     int ms;    /* Assuming this is >= 16 bits */
144 } TimeMark;
145
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148                          char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150                       char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155                       int toX, int toY));
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163                                                                                 Board board));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167                    /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179                            char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181                         int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
222
223 #ifdef WIN32
224        extern void ConsoleCreate();
225 #endif
226
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
230
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
237
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 int endPV = -1;
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
245 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
246 Boolean partnerUp;
247 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
248 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
249 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
250 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
251 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
252 int opponentKibitzes;
253 int lastSavedGame; /* [HGM] save: ID of game */
254 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
255 extern int chatCount;
256 int chattingPartner;
257 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
258
259 /* States for ics_getting_history */
260 #define H_FALSE 0
261 #define H_REQUESTED 1
262 #define H_GOT_REQ_HEADER 2
263 #define H_GOT_UNREQ_HEADER 3
264 #define H_GETTING_MOVES 4
265 #define H_GOT_UNWANTED_HEADER 5
266
267 /* whosays values for GameEnds */
268 #define GE_ICS 0
269 #define GE_ENGINE 1
270 #define GE_PLAYER 2
271 #define GE_FILE 3
272 #define GE_XBOARD 4
273 #define GE_ENGINE1 5
274 #define GE_ENGINE2 6
275
276 /* Maximum number of games in a cmail message */
277 #define CMAIL_MAX_GAMES 20
278
279 /* Different types of move when calling RegisterMove */
280 #define CMAIL_MOVE   0
281 #define CMAIL_RESIGN 1
282 #define CMAIL_DRAW   2
283 #define CMAIL_ACCEPT 3
284
285 /* Different types of result to remember for each game */
286 #define CMAIL_NOT_RESULT 0
287 #define CMAIL_OLD_RESULT 1
288 #define CMAIL_NEW_RESULT 2
289
290 /* Telnet protocol constants */
291 #define TN_WILL 0373
292 #define TN_WONT 0374
293 #define TN_DO   0375
294 #define TN_DONT 0376
295 #define TN_IAC  0377
296 #define TN_ECHO 0001
297 #define TN_SGA  0003
298 #define TN_PORT 23
299
300 /* [AS] */
301 static char * safeStrCpy( char * dst, const char * src, size_t count )
302 {
303     assert( dst != NULL );
304     assert( src != NULL );
305     assert( count > 0 );
306
307     strncpy( dst, src, count );
308     dst[ count-1 ] = '\0';
309     return dst;
310 }
311
312 /* Some compiler can't cast u64 to double
313  * This function do the job for us:
314
315  * We use the highest bit for cast, this only
316  * works if the highest bit is not
317  * in use (This should not happen)
318  *
319  * We used this for all compiler
320  */
321 double
322 u64ToDouble(u64 value)
323 {
324   double r;
325   u64 tmp = value & u64Const(0x7fffffffffffffff);
326   r = (double)(s64)tmp;
327   if (value & u64Const(0x8000000000000000))
328        r +=  9.2233720368547758080e18; /* 2^63 */
329  return r;
330 }
331
332 /* Fake up flags for now, as we aren't keeping track of castling
333    availability yet. [HGM] Change of logic: the flag now only
334    indicates the type of castlings allowed by the rule of the game.
335    The actual rights themselves are maintained in the array
336    castlingRights, as part of the game history, and are not probed
337    by this function.
338  */
339 int
340 PosFlags(index)
341 {
342   int flags = F_ALL_CASTLE_OK;
343   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
344   switch (gameInfo.variant) {
345   case VariantSuicide:
346     flags &= ~F_ALL_CASTLE_OK;
347   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
348     flags |= F_IGNORE_CHECK;
349   case VariantLosers:
350     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351     break;
352   case VariantAtomic:
353     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
354     break;
355   case VariantKriegspiel:
356     flags |= F_KRIEGSPIEL_CAPTURE;
357     break;
358   case VariantCapaRandom: 
359   case VariantFischeRandom:
360     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
361   case VariantNoCastle:
362   case VariantShatranj:
363   case VariantCourier:
364   case VariantMakruk:
365     flags &= ~F_ALL_CASTLE_OK;
366     break;
367   default:
368     break;
369   }
370   return flags;
371 }
372
373 FILE *gameFileFP, *debugFP;
374
375 /* 
376     [AS] Note: sometimes, the sscanf() function is used to parse the input
377     into a fixed-size buffer. Because of this, we must be prepared to
378     receive strings as long as the size of the input buffer, which is currently
379     set to 4K for Windows and 8K for the rest.
380     So, we must either allocate sufficiently large buffers here, or
381     reduce the size of the input buffer in the input reading part.
382 */
383
384 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
385 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
386 char thinkOutput1[MSG_SIZ*10];
387
388 ChessProgramState first, second;
389
390 /* premove variables */
391 int premoveToX = 0;
392 int premoveToY = 0;
393 int premoveFromX = 0;
394 int premoveFromY = 0;
395 int premovePromoChar = 0;
396 int gotPremove = 0;
397 Boolean alarmSounded;
398 /* end premove variables */
399
400 char *ics_prefix = "$";
401 int ics_type = ICS_GENERIC;
402
403 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
404 int pauseExamForwardMostMove = 0;
405 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
406 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
407 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
408 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
409 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
410 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
411 int whiteFlag = FALSE, blackFlag = FALSE;
412 int userOfferedDraw = FALSE;
413 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
414 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
415 int cmailMoveType[CMAIL_MAX_GAMES];
416 long ics_clock_paused = 0;
417 ProcRef icsPR = NoProc, cmailPR = NoProc;
418 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
419 GameMode gameMode = BeginningOfGame;
420 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
421 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
422 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
423 int hiddenThinkOutputState = 0; /* [AS] */
424 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
425 int adjudicateLossPlies = 6;
426 char white_holding[64], black_holding[64];
427 TimeMark lastNodeCountTime;
428 long lastNodeCount=0;
429 int have_sent_ICS_logon = 0;
430 int movesPerSession;
431 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
432 long timeControl_2; /* [AS] Allow separate time controls */
433 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
434 long timeRemaining[2][MAX_MOVES];
435 int matchGame = 0;
436 TimeMark programStartTime;
437 char ics_handle[MSG_SIZ];
438 int have_set_title = 0;
439
440 /* animateTraining preserves the state of appData.animate
441  * when Training mode is activated. This allows the
442  * response to be animated when appData.animate == TRUE and
443  * appData.animateDragging == TRUE.
444  */
445 Boolean animateTraining;
446
447 GameInfo gameInfo;
448
449 AppData appData;
450
451 Board boards[MAX_MOVES];
452 /* [HGM] Following 7 needed for accurate legality tests: */
453 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
454 signed char  initialRights[BOARD_FILES];
455 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
456 int   initialRulePlies, FENrulePlies;
457 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int loadFlag = 0; 
459 int shuffleOpenings;
460 int mute; // mute all sounds
461
462 // [HGM] vari: next 12 to save and restore variations
463 #define MAX_VARIATIONS 10
464 int framePtr = MAX_MOVES-1; // points to free stack entry
465 int storedGames = 0;
466 int savedFirst[MAX_VARIATIONS];
467 int savedLast[MAX_VARIATIONS];
468 int savedFramePtr[MAX_VARIATIONS];
469 char *savedDetails[MAX_VARIATIONS];
470 ChessMove savedResult[MAX_VARIATIONS];
471
472 void PushTail P((int firstMove, int lastMove));
473 Boolean PopTail P((Boolean annotate));
474 void CleanupTail P((void));
475
476 ChessSquare  FIDEArray[2][BOARD_FILES] = {
477     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
478         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
479     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
480         BlackKing, BlackBishop, BlackKnight, BlackRook }
481 };
482
483 ChessSquare twoKingsArray[2][BOARD_FILES] = {
484     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
485         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
486     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
487         BlackKing, BlackKing, BlackKnight, BlackRook }
488 };
489
490 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
491     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
492         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
493     { BlackRook, BlackMan, BlackBishop, BlackQueen,
494         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 };
496
497 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
498     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
499         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
500     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
501         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 };
503
504 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
505     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
506         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
508         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 };
510
511 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
512     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
513         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackMan, BlackFerz,
515         BlackKing, BlackMan, BlackKnight, BlackRook }
516 };
517
518
519 #if (BOARD_FILES>=10)
520 ChessSquare ShogiArray[2][BOARD_FILES] = {
521     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
522         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
523     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
524         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 };
526
527 ChessSquare XiangqiArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
529         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
530     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
531         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 };
533
534 ChessSquare CapablancaArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, 
536         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, 
538         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 };
540
541 ChessSquare GreatArray[2][BOARD_FILES] = {
542     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, 
543         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
544     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, 
545         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 };
547
548 ChessSquare JanusArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, 
550         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
551     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, 
552         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 };
554
555 #ifdef GOTHIC
556 ChessSquare GothicArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, 
558         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, 
560         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 };
562 #else // !GOTHIC
563 #define GothicArray CapablancaArray
564 #endif // !GOTHIC
565
566 #ifdef FALCON
567 ChessSquare FalconArray[2][BOARD_FILES] = {
568     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, 
569         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, 
571         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 };
573 #else // !FALCON
574 #define FalconArray CapablancaArray
575 #endif // !FALCON
576
577 #else // !(BOARD_FILES>=10)
578 #define XiangqiPosition FIDEArray
579 #define CapablancaArray FIDEArray
580 #define GothicArray FIDEArray
581 #define GreatArray FIDEArray
582 #endif // !(BOARD_FILES>=10)
583
584 #if (BOARD_FILES>=12)
585 ChessSquare CourierArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
589         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
590 };
591 #else // !(BOARD_FILES>=12)
592 #define CourierArray CapablancaArray
593 #endif // !(BOARD_FILES>=12)
594
595
596 Board initialPosition;
597
598
599 /* Convert str to a rating. Checks for special cases of "----",
600
601    "++++", etc. Also strips ()'s */
602 int
603 string_to_rating(str)
604   char *str;
605 {
606   while(*str && !isdigit(*str)) ++str;
607   if (!*str)
608     return 0;   /* One of the special "no rating" cases */
609   else
610     return atoi(str);
611 }
612
613 void
614 ClearProgramStats()
615 {
616     /* Init programStats */
617     programStats.movelist[0] = 0;
618     programStats.depth = 0;
619     programStats.nr_moves = 0;
620     programStats.moves_left = 0;
621     programStats.nodes = 0;
622     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
623     programStats.score = 0;
624     programStats.got_only_move = 0;
625     programStats.got_fail = 0;
626     programStats.line_is_book = 0;
627 }
628
629 void
630 InitBackEnd1()
631 {
632     int matched, min, sec;
633
634     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
635
636     GetTimeMark(&programStartTime);
637     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638
639     ClearProgramStats();
640     programStats.ok_to_send = 1;
641     programStats.seen_stat = 0;
642
643     /*
644      * Initialize game list
645      */
646     ListNew(&gameList);
647
648
649     /*
650      * Internet chess server status
651      */
652     if (appData.icsActive) {
653         appData.matchMode = FALSE;
654         appData.matchGames = 0;
655 #if ZIPPY       
656         appData.noChessProgram = !appData.zippyPlay;
657 #else
658         appData.zippyPlay = FALSE;
659         appData.zippyTalk = FALSE;
660         appData.noChessProgram = TRUE;
661 #endif
662         if (*appData.icsHelper != NULLCHAR) {
663             appData.useTelnet = TRUE;
664             appData.telnetProgram = appData.icsHelper;
665         }
666     } else {
667         appData.zippyTalk = appData.zippyPlay = FALSE;
668     }
669
670     /* [AS] Initialize pv info list [HGM] and game state */
671     {
672         int i, j;
673
674         for( i=0; i<=framePtr; i++ ) {
675             pvInfoList[i].depth = -1;
676             boards[i][EP_STATUS] = EP_NONE;
677             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
678         }
679     }
680
681     /*
682      * Parse timeControl resource
683      */
684     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
685                           appData.movesPerSession)) {
686         char buf[MSG_SIZ];
687         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
688         DisplayFatalError(buf, 0, 2);
689     }
690
691     /*
692      * Parse searchTime resource
693      */
694     if (*appData.searchTime != NULLCHAR) {
695         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
696         if (matched == 1) {
697             searchTime = min * 60;
698         } else if (matched == 2) {
699             searchTime = min * 60 + sec;
700         } else {
701             char buf[MSG_SIZ];
702             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
703             DisplayFatalError(buf, 0, 2);
704         }
705     }
706
707     /* [AS] Adjudication threshold */
708     adjudicateLossThreshold = appData.adjudicateLossThreshold;
709     
710     first.which = "first";
711     second.which = "second";
712     first.maybeThinking = second.maybeThinking = FALSE;
713     first.pr = second.pr = NoProc;
714     first.isr = second.isr = NULL;
715     first.sendTime = second.sendTime = 2;
716     first.sendDrawOffers = 1;
717     if (appData.firstPlaysBlack) {
718         first.twoMachinesColor = "black\n";
719         second.twoMachinesColor = "white\n";
720     } else {
721         first.twoMachinesColor = "white\n";
722         second.twoMachinesColor = "black\n";
723     }
724     first.program = appData.firstChessProgram;
725     second.program = appData.secondChessProgram;
726     first.host = appData.firstHost;
727     second.host = appData.secondHost;
728     first.dir = appData.firstDirectory;
729     second.dir = appData.secondDirectory;
730     first.other = &second;
731     second.other = &first;
732     first.initString = appData.initString;
733     second.initString = appData.secondInitString;
734     first.computerString = appData.firstComputerString;
735     second.computerString = appData.secondComputerString;
736     first.useSigint = second.useSigint = TRUE;
737     first.useSigterm = second.useSigterm = TRUE;
738     first.reuse = appData.reuseFirst;
739     second.reuse = appData.reuseSecond;
740     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
741     second.nps = appData.secondNPS;
742     first.useSetboard = second.useSetboard = FALSE;
743     first.useSAN = second.useSAN = FALSE;
744     first.usePing = second.usePing = FALSE;
745     first.lastPing = second.lastPing = 0;
746     first.lastPong = second.lastPong = 0;
747     first.usePlayother = second.usePlayother = FALSE;
748     first.useColors = second.useColors = TRUE;
749     first.useUsermove = second.useUsermove = FALSE;
750     first.sendICS = second.sendICS = FALSE;
751     first.sendName = second.sendName = appData.icsActive;
752     first.sdKludge = second.sdKludge = FALSE;
753     first.stKludge = second.stKludge = FALSE;
754     TidyProgramName(first.program, first.host, first.tidy);
755     TidyProgramName(second.program, second.host, second.tidy);
756     first.matchWins = second.matchWins = 0;
757     strcpy(first.variants, appData.variant);
758     strcpy(second.variants, appData.variant);
759     first.analysisSupport = second.analysisSupport = 2; /* detect */
760     first.analyzing = second.analyzing = FALSE;
761     first.initDone = second.initDone = FALSE;
762
763     /* New features added by Tord: */
764     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
765     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
766     /* End of new features added by Tord. */
767     first.fenOverride  = appData.fenOverride1;
768     second.fenOverride = appData.fenOverride2;
769
770     /* [HGM] time odds: set factor for each machine */
771     first.timeOdds  = appData.firstTimeOdds;
772     second.timeOdds = appData.secondTimeOdds;
773     { float norm = 1;
774         if(appData.timeOddsMode) {
775             norm = first.timeOdds;
776             if(norm > second.timeOdds) norm = second.timeOdds;
777         }
778         first.timeOdds /= norm;
779         second.timeOdds /= norm;
780     }
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     first.accumulateTC = appData.firstAccumulateTC;
784     second.accumulateTC = appData.secondAccumulateTC;
785     first.maxNrOfSessions = second.maxNrOfSessions = 1;
786
787     /* [HGM] debug */
788     first.debug = second.debug = FALSE;
789     first.supportsNPS = second.supportsNPS = UNKNOWN;
790
791     /* [HGM] options */
792     first.optionSettings  = appData.firstOptions;
793     second.optionSettings = appData.secondOptions;
794
795     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
796     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
797     first.isUCI = appData.firstIsUCI; /* [AS] */
798     second.isUCI = appData.secondIsUCI; /* [AS] */
799     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
800     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
801
802     if (appData.firstProtocolVersion > PROTOVER ||
803         appData.firstProtocolVersion < 1) {
804       char buf[MSG_SIZ];
805       sprintf(buf, _("protocol version %d not supported"),
806               appData.firstProtocolVersion);
807       DisplayFatalError(buf, 0, 2);
808     } else {
809       first.protocolVersion = appData.firstProtocolVersion;
810     }
811
812     if (appData.secondProtocolVersion > PROTOVER ||
813         appData.secondProtocolVersion < 1) {
814       char buf[MSG_SIZ];
815       sprintf(buf, _("protocol version %d not supported"),
816               appData.secondProtocolVersion);
817       DisplayFatalError(buf, 0, 2);
818     } else {
819       second.protocolVersion = appData.secondProtocolVersion;
820     }
821
822     if (appData.icsActive) {
823         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
824 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
825     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
826         appData.clockMode = FALSE;
827         first.sendTime = second.sendTime = 0;
828     }
829     
830 #if ZIPPY
831     /* Override some settings from environment variables, for backward
832        compatibility.  Unfortunately it's not feasible to have the env
833        vars just set defaults, at least in xboard.  Ugh.
834     */
835     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
836       ZippyInit();
837     }
838 #endif
839     
840     if (appData.noChessProgram) {
841         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
842         sprintf(programVersion, "%s", PACKAGE_STRING);
843     } else {
844       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847     }
848
849     if (!appData.icsActive) {
850       char buf[MSG_SIZ];
851       /* Check for variants that are supported only in ICS mode,
852          or not at all.  Some that are accepted here nevertheless
853          have bugs; see comments below.
854       */
855       VariantClass variant = StringToVariant(appData.variant);
856       switch (variant) {
857       case VariantBughouse:     /* need four players and two boards */
858       case VariantKriegspiel:   /* need to hide pieces and move details */
859       /* case VariantFischeRandom: (Fabien: moved below) */
860         sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861         DisplayFatalError(buf, 0, 2);
862         return;
863
864       case VariantUnknown:
865       case VariantLoadable:
866       case Variant29:
867       case Variant30:
868       case Variant31:
869       case Variant32:
870       case Variant33:
871       case Variant34:
872       case Variant35:
873       case Variant36:
874       default:
875         sprintf(buf, _("Unknown variant name %s"), appData.variant);
876         DisplayFatalError(buf, 0, 2);
877         return;
878
879       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
880       case VariantFairy:      /* [HGM] TestLegality definitely off! */
881       case VariantGothic:     /* [HGM] should work */
882       case VariantCapablanca: /* [HGM] should work */
883       case VariantCourier:    /* [HGM] initial forced moves not implemented */
884       case VariantShogi:      /* [HGM] drops not tested for legality */
885       case VariantKnightmate: /* [HGM] should work */
886       case VariantCylinder:   /* [HGM] untested */
887       case VariantFalcon:     /* [HGM] untested */
888       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889                                  offboard interposition not understood */
890       case VariantNormal:     /* definitely works! */
891       case VariantWildCastle: /* pieces not automatically shuffled */
892       case VariantNoCastle:   /* pieces not automatically shuffled */
893       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894       case VariantLosers:     /* should work except for win condition,
895                                  and doesn't know captures are mandatory */
896       case VariantSuicide:    /* should work except for win condition,
897                                  and doesn't know captures are mandatory */
898       case VariantGiveaway:   /* should work except for win condition,
899                                  and doesn't know captures are mandatory */
900       case VariantTwoKings:   /* should work */
901       case VariantAtomic:     /* should work except for win condition */
902       case Variant3Check:     /* should work except for win condition */
903       case VariantShatranj:   /* should work except for all win conditions */
904       case VariantMakruk:     /* should work except for daw countdown */
905       case VariantBerolina:   /* might work if TestLegality is off */
906       case VariantCapaRandom: /* should work */
907       case VariantJanus:      /* should work */
908       case VariantSuper:      /* experimental */
909       case VariantGreat:      /* experimental, requires legality testing to be off */
910         break;
911       }
912     }
913
914     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
915     InitEngineUCI( installDir, &second );
916 }
917
918 int NextIntegerFromString( char ** str, long * value )
919 {
920     int result = -1;
921     char * s = *str;
922
923     while( *s == ' ' || *s == '\t' ) {
924         s++;
925     }
926
927     *value = 0;
928
929     if( *s >= '0' && *s <= '9' ) {
930         while( *s >= '0' && *s <= '9' ) {
931             *value = *value * 10 + (*s - '0');
932             s++;
933         }
934
935         result = 0;
936     }
937
938     *str = s;
939
940     return result;
941 }
942
943 int NextTimeControlFromString( char ** str, long * value )
944 {
945     long temp;
946     int result = NextIntegerFromString( str, &temp );
947
948     if( result == 0 ) {
949         *value = temp * 60; /* Minutes */
950         if( **str == ':' ) {
951             (*str)++;
952             result = NextIntegerFromString( str, &temp );
953             *value += temp; /* Seconds */
954         }
955     }
956
957     return result;
958 }
959
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 {   /* [HGM] routine added to read '+moves/time' for secondary time control */
962     int result = -1; long temp, temp2;
963
964     if(**str != '+') return -1; // old params remain in force!
965     (*str)++;
966     if( NextTimeControlFromString( str, &temp ) ) return -1;
967
968     if(**str != '/') {
969         /* time only: incremental or sudden-death time control */
970         if(**str == '+') { /* increment follows; read it */
971             (*str)++;
972             if(result = NextIntegerFromString( str, &temp2)) return -1;
973             *inc = temp2 * 1000;
974         } else *inc = 0;
975         *moves = 0; *tc = temp * 1000; 
976         return 0;
977     } else if(temp % 60 != 0) return -1;     /* moves was given as min:sec */
978
979     (*str)++; /* classical time control */
980     result = NextTimeControlFromString( str, &temp2);
981     if(result == 0) {
982         *moves = temp/60;
983         *tc    = temp2 * 1000;
984         *inc   = 0;
985     }
986     return result;
987 }
988
989 int GetTimeQuota(int movenr)
990 {   /* [HGM] get time to add from the multi-session time-control string */
991     int moves=1; /* kludge to force reading of first session */
992     long time, increment;
993     char *s = fullTimeControlString;
994
995     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996     do {
997         if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999         if(movenr == -1) return time;    /* last move before new session     */
1000         if(!moves) return increment;     /* current session is incremental   */
1001         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002     } while(movenr >= -1);               /* try again for next session       */
1003
1004     return 0; // no new time quota on this move
1005 }
1006
1007 int
1008 ParseTimeControl(tc, ti, mps)
1009      char *tc;
1010      int ti;
1011      int mps;
1012 {
1013   long tc1;
1014   long tc2;
1015   char buf[MSG_SIZ];
1016   
1017   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018   if(ti > 0) {
1019     if(mps)
1020       sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1021     else sprintf(buf, "+%s+%d", tc, ti);
1022   } else {
1023     if(mps)
1024              sprintf(buf, "+%d/%s", mps, tc);
1025     else sprintf(buf, "+%s", tc);
1026   }
1027   fullTimeControlString = StrSave(buf);
1028   
1029   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1030     return FALSE;
1031   }
1032   
1033   if( *tc == '/' ) {
1034     /* Parse second time control */
1035     tc++;
1036     
1037     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1038       return FALSE;
1039     }
1040     
1041     if( tc2 == 0 ) {
1042       return FALSE;
1043     }
1044     
1045     timeControl_2 = tc2 * 1000;
1046   }
1047   else {
1048     timeControl_2 = 0;
1049   }
1050   
1051   if( tc1 == 0 ) {
1052     return FALSE;
1053   }
1054   
1055   timeControl = tc1 * 1000;
1056   
1057   if (ti >= 0) {
1058     timeIncrement = ti * 1000;  /* convert to ms */
1059     movesPerSession = 0;
1060   } else {
1061     timeIncrement = 0;
1062     movesPerSession = mps;
1063   }
1064   return TRUE;
1065 }
1066
1067 void
1068 InitBackEnd2()
1069 {
1070     if (appData.debugMode) {
1071         fprintf(debugFP, "%s\n", programVersion);
1072     }
1073
1074     set_cont_sequence(appData.wrapContSeq);
1075     if (appData.matchGames > 0) {
1076         appData.matchMode = TRUE;
1077     } else if (appData.matchMode) {
1078         appData.matchGames = 1;
1079     }
1080     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1081         appData.matchGames = appData.sameColorGames;
1082     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1083         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1084         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085     }
1086     Reset(TRUE, FALSE);
1087     if (appData.noChessProgram || first.protocolVersion == 1) {
1088       InitBackEnd3();
1089     } else {
1090       /* kludge: allow timeout for initial "feature" commands */
1091       FreezeUI();
1092       DisplayMessage("", _("Starting chess program"));
1093       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1094     }
1095 }
1096
1097 void
1098 InitBackEnd3 P((void))
1099 {
1100     GameMode initialMode;
1101     char buf[MSG_SIZ];
1102     int err;
1103
1104     InitChessProgram(&first, startedFromSetupPosition);
1105
1106
1107     if (appData.icsActive) {
1108 #ifdef WIN32
1109         /* [DM] Make a console window if needed [HGM] merged ifs */
1110         ConsoleCreate(); 
1111 #endif
1112         err = establish();
1113         if (err != 0) {
1114             if (*appData.icsCommPort != NULLCHAR) {
1115                 sprintf(buf, _("Could not open comm port %s"),  
1116                         appData.icsCommPort);
1117             } else {
1118                 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),  
1119                         appData.icsHost, appData.icsPort);
1120             }
1121             DisplayFatalError(buf, err, 1);
1122             return;
1123         }
1124         SetICSMode();
1125         telnetISR =
1126           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1127         fromUserISR =
1128           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1129         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1130             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1131     } else if (appData.noChessProgram) {
1132         SetNCPMode();
1133     } else {
1134         SetGNUMode();
1135     }
1136
1137     if (*appData.cmailGameName != NULLCHAR) {
1138         SetCmailMode();
1139         OpenLoopback(&cmailPR);
1140         cmailISR =
1141           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142     }
1143     
1144     ThawUI();
1145     DisplayMessage("", "");
1146     if (StrCaseCmp(appData.initialMode, "") == 0) {
1147       initialMode = BeginningOfGame;
1148     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1149       initialMode = TwoMachinesPlay;
1150     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1151       initialMode = AnalyzeFile; 
1152     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1153       initialMode = AnalyzeMode;
1154     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1155       initialMode = MachinePlaysWhite;
1156     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1157       initialMode = MachinePlaysBlack;
1158     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1159       initialMode = EditGame;
1160     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1161       initialMode = EditPosition;
1162     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1163       initialMode = Training;
1164     } else {
1165       sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1166       DisplayFatalError(buf, 0, 2);
1167       return;
1168     }
1169
1170     if (appData.matchMode) {
1171         /* Set up machine vs. machine match */
1172         if (appData.noChessProgram) {
1173             DisplayFatalError(_("Can't have a match with no chess programs"),
1174                               0, 2);
1175             return;
1176         }
1177         matchMode = TRUE;
1178         matchGame = 1;
1179         if (*appData.loadGameFile != NULLCHAR) {
1180             int index = appData.loadGameIndex; // [HGM] autoinc
1181             if(index<0) lastIndex = index = 1;
1182             if (!LoadGameFromFile(appData.loadGameFile,
1183                                   index,
1184                                   appData.loadGameFile, FALSE)) {
1185                 DisplayFatalError(_("Bad game file"), 0, 1);
1186                 return;
1187             }
1188         } else if (*appData.loadPositionFile != NULLCHAR) {
1189             int index = appData.loadPositionIndex; // [HGM] autoinc
1190             if(index<0) lastIndex = index = 1;
1191             if (!LoadPositionFromFile(appData.loadPositionFile,
1192                                       index,
1193                                       appData.loadPositionFile)) {
1194                 DisplayFatalError(_("Bad position file"), 0, 1);
1195                 return;
1196             }
1197         }
1198         TwoMachinesEvent();
1199     } else if (*appData.cmailGameName != NULLCHAR) {
1200         /* Set up cmail mode */
1201         ReloadCmailMsgEvent(TRUE);
1202     } else {
1203         /* Set up other modes */
1204         if (initialMode == AnalyzeFile) {
1205           if (*appData.loadGameFile == NULLCHAR) {
1206             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207             return;
1208           }
1209         }
1210         if (*appData.loadGameFile != NULLCHAR) {
1211             (void) LoadGameFromFile(appData.loadGameFile,
1212                                     appData.loadGameIndex,
1213                                     appData.loadGameFile, TRUE);
1214         } else if (*appData.loadPositionFile != NULLCHAR) {
1215             (void) LoadPositionFromFile(appData.loadPositionFile,
1216                                         appData.loadPositionIndex,
1217                                         appData.loadPositionFile);
1218             /* [HGM] try to make self-starting even after FEN load */
1219             /* to allow automatic setup of fairy variants with wtm */
1220             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1221                 gameMode = BeginningOfGame;
1222                 setboardSpoiledMachineBlack = 1;
1223             }
1224             /* [HGM] loadPos: make that every new game uses the setup */
1225             /* from file as long as we do not switch variant          */
1226             if(!blackPlaysFirst) {
1227                 startedFromPositionFile = TRUE;
1228                 CopyBoard(filePosition, boards[0]);
1229             }
1230         }
1231         if (initialMode == AnalyzeMode) {
1232           if (appData.noChessProgram) {
1233             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234             return;
1235           }
1236           if (appData.icsActive) {
1237             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238             return;
1239           }
1240           AnalyzeModeEvent();
1241         } else if (initialMode == AnalyzeFile) {
1242           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1243           ShowThinkingEvent();
1244           AnalyzeFileEvent();
1245           AnalysisPeriodicEvent(1);
1246         } else if (initialMode == MachinePlaysWhite) {
1247           if (appData.noChessProgram) {
1248             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249                               0, 2);
1250             return;
1251           }
1252           if (appData.icsActive) {
1253             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254                               0, 2);
1255             return;
1256           }
1257           MachineWhiteEvent();
1258         } else if (initialMode == MachinePlaysBlack) {
1259           if (appData.noChessProgram) {
1260             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261                               0, 2);
1262             return;
1263           }
1264           if (appData.icsActive) {
1265             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266                               0, 2);
1267             return;
1268           }
1269           MachineBlackEvent();
1270         } else if (initialMode == TwoMachinesPlay) {
1271           if (appData.noChessProgram) {
1272             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273                               0, 2);
1274             return;
1275           }
1276           if (appData.icsActive) {
1277             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1278                               0, 2);
1279             return;
1280           }
1281           TwoMachinesEvent();
1282         } else if (initialMode == EditGame) {
1283           EditGameEvent();
1284         } else if (initialMode == EditPosition) {
1285           EditPositionEvent();
1286         } else if (initialMode == Training) {
1287           if (*appData.loadGameFile == NULLCHAR) {
1288             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1289             return;
1290           }
1291           TrainingEvent();
1292         }
1293     }
1294 }
1295
1296 /*
1297  * Establish will establish a contact to a remote host.port.
1298  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1299  *  used to talk to the host.
1300  * Returns 0 if okay, error code if not.
1301  */
1302 int
1303 establish()
1304 {
1305     char buf[MSG_SIZ];
1306
1307     if (*appData.icsCommPort != NULLCHAR) {
1308         /* Talk to the host through a serial comm port */
1309         return OpenCommPort(appData.icsCommPort, &icsPR);
1310
1311     } else if (*appData.gateway != NULLCHAR) {
1312         if (*appData.remoteShell == NULLCHAR) {
1313             /* Use the rcmd protocol to run telnet program on a gateway host */
1314             snprintf(buf, sizeof(buf), "%s %s %s",
1315                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1316             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317
1318         } else {
1319             /* Use the rsh program to run telnet program on a gateway host */
1320             if (*appData.remoteUser == NULLCHAR) {
1321                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1322                         appData.gateway, appData.telnetProgram,
1323                         appData.icsHost, appData.icsPort);
1324             } else {
1325                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1326                         appData.remoteShell, appData.gateway, 
1327                         appData.remoteUser, appData.telnetProgram,
1328                         appData.icsHost, appData.icsPort);
1329             }
1330             return StartChildProcess(buf, "", &icsPR);
1331
1332         }
1333     } else if (appData.useTelnet) {
1334         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335
1336     } else {
1337         /* TCP socket interface differs somewhat between
1338            Unix and NT; handle details in the front end.
1339            */
1340         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1341     }
1342 }
1343
1344 void
1345 show_bytes(fp, buf, count)
1346      FILE *fp;
1347      char *buf;
1348      int count;
1349 {
1350     while (count--) {
1351         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1352             fprintf(fp, "\\%03o", *buf & 0xff);
1353         } else {
1354             putc(*buf, fp);
1355         }
1356         buf++;
1357     }
1358     fflush(fp);
1359 }
1360
1361 /* Returns an errno value */
1362 int
1363 OutputMaybeTelnet(pr, message, count, outError)
1364      ProcRef pr;
1365      char *message;
1366      int count;
1367      int *outError;
1368 {
1369     char buf[8192], *p, *q, *buflim;
1370     int left, newcount, outcount;
1371
1372     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1373         *appData.gateway != NULLCHAR) {
1374         if (appData.debugMode) {
1375             fprintf(debugFP, ">ICS: ");
1376             show_bytes(debugFP, message, count);
1377             fprintf(debugFP, "\n");
1378         }
1379         return OutputToProcess(pr, message, count, outError);
1380     }
1381
1382     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1383     p = message;
1384     q = buf;
1385     left = count;
1386     newcount = 0;
1387     while (left) {
1388         if (q >= buflim) {
1389             if (appData.debugMode) {
1390                 fprintf(debugFP, ">ICS: ");
1391                 show_bytes(debugFP, buf, newcount);
1392                 fprintf(debugFP, "\n");
1393             }
1394             outcount = OutputToProcess(pr, buf, newcount, outError);
1395             if (outcount < newcount) return -1; /* to be sure */
1396             q = buf;
1397             newcount = 0;
1398         }
1399         if (*p == '\n') {
1400             *q++ = '\r';
1401             newcount++;
1402         } else if (((unsigned char) *p) == TN_IAC) {
1403             *q++ = (char) TN_IAC;
1404             newcount ++;
1405         }
1406         *q++ = *p++;
1407         newcount++;
1408         left--;
1409     }
1410     if (appData.debugMode) {
1411         fprintf(debugFP, ">ICS: ");
1412         show_bytes(debugFP, buf, newcount);
1413         fprintf(debugFP, "\n");
1414     }
1415     outcount = OutputToProcess(pr, buf, newcount, outError);
1416     if (outcount < newcount) return -1; /* to be sure */
1417     return count;
1418 }
1419
1420 void
1421 read_from_player(isr, closure, message, count, error)
1422      InputSourceRef isr;
1423      VOIDSTAR closure;
1424      char *message;
1425      int count;
1426      int error;
1427 {
1428     int outError, outCount;
1429     static int gotEof = 0;
1430
1431     /* Pass data read from player on to ICS */
1432     if (count > 0) {
1433         gotEof = 0;
1434         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1435         if (outCount < count) {
1436             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1437         }
1438     } else if (count < 0) {
1439         RemoveInputSource(isr);
1440         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1441     } else if (gotEof++ > 0) {
1442         RemoveInputSource(isr);
1443         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1444     }
1445 }
1446
1447 void
1448 KeepAlive()
1449 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1450     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1451     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1452     SendToICS("date\n");
1453     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 }
1455
1456 /* added routine for printf style output to ics */
1457 void ics_printf(char *format, ...)
1458 {
1459     char buffer[MSG_SIZ];
1460     va_list args;
1461
1462     va_start(args, format);
1463     vsnprintf(buffer, sizeof(buffer), format, args);
1464     buffer[sizeof(buffer)-1] = '\0';
1465     SendToICS(buffer);
1466     va_end(args);
1467 }
1468
1469 void
1470 SendToICS(s)
1471      char *s;
1472 {
1473     int count, outCount, outError;
1474
1475     if (icsPR == NULL) return;
1476
1477     count = strlen(s);
1478     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1479     if (outCount < count) {
1480         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481     }
1482 }
1483
1484 /* This is used for sending logon scripts to the ICS. Sending
1485    without a delay causes problems when using timestamp on ICC
1486    (at least on my machine). */
1487 void
1488 SendToICSDelayed(s,msdelay)
1489      char *s;
1490      long msdelay;
1491 {
1492     int count, outCount, outError;
1493
1494     if (icsPR == NULL) return;
1495
1496     count = strlen(s);
1497     if (appData.debugMode) {
1498         fprintf(debugFP, ">ICS: ");
1499         show_bytes(debugFP, s, count);
1500         fprintf(debugFP, "\n");
1501     }
1502     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1503                                       msdelay);
1504     if (outCount < count) {
1505         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506     }
1507 }
1508
1509
1510 /* Remove all highlighting escape sequences in s
1511    Also deletes any suffix starting with '(' 
1512    */
1513 char *
1514 StripHighlightAndTitle(s)
1515      char *s;
1516 {
1517     static char retbuf[MSG_SIZ];
1518     char *p = retbuf;
1519
1520     while (*s != NULLCHAR) {
1521         while (*s == '\033') {
1522             while (*s != NULLCHAR && !isalpha(*s)) s++;
1523             if (*s != NULLCHAR) s++;
1524         }
1525         while (*s != NULLCHAR && *s != '\033') {
1526             if (*s == '(' || *s == '[') {
1527                 *p = NULLCHAR;
1528                 return retbuf;
1529             }
1530             *p++ = *s++;
1531         }
1532     }
1533     *p = NULLCHAR;
1534     return retbuf;
1535 }
1536
1537 /* Remove all highlighting escape sequences in s */
1538 char *
1539 StripHighlight(s)
1540      char *s;
1541 {
1542     static char retbuf[MSG_SIZ];
1543     char *p = retbuf;
1544
1545     while (*s != NULLCHAR) {
1546         while (*s == '\033') {
1547             while (*s != NULLCHAR && !isalpha(*s)) s++;
1548             if (*s != NULLCHAR) s++;
1549         }
1550         while (*s != NULLCHAR && *s != '\033') {
1551             *p++ = *s++;
1552         }
1553     }
1554     *p = NULLCHAR;
1555     return retbuf;
1556 }
1557
1558 char *variantNames[] = VARIANT_NAMES;
1559 char *
1560 VariantName(v)
1561      VariantClass v;
1562 {
1563     return variantNames[v];
1564 }
1565
1566
1567 /* Identify a variant from the strings the chess servers use or the
1568    PGN Variant tag names we use. */
1569 VariantClass
1570 StringToVariant(e)
1571      char *e;
1572 {
1573     char *p;
1574     int wnum = -1;
1575     VariantClass v = VariantNormal;
1576     int i, found = FALSE;
1577     char buf[MSG_SIZ];
1578
1579     if (!e) return v;
1580
1581     /* [HGM] skip over optional board-size prefixes */
1582     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1583         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1584         while( *e++ != '_');
1585     }
1586
1587     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588         v = VariantNormal;
1589         found = TRUE;
1590     } else
1591     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1592       if (StrCaseStr(e, variantNames[i])) {
1593         v = (VariantClass) i;
1594         found = TRUE;
1595         break;
1596       }
1597     }
1598
1599     if (!found) {
1600       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1601           || StrCaseStr(e, "wild/fr") 
1602           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1603         v = VariantFischeRandom;
1604       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1605                  (i = 1, p = StrCaseStr(e, "w"))) {
1606         p += i;
1607         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1608         if (isdigit(*p)) {
1609           wnum = atoi(p);
1610         } else {
1611           wnum = -1;
1612         }
1613         switch (wnum) {
1614         case 0: /* FICS only, actually */
1615         case 1:
1616           /* Castling legal even if K starts on d-file */
1617           v = VariantWildCastle;
1618           break;
1619         case 2:
1620         case 3:
1621         case 4:
1622           /* Castling illegal even if K & R happen to start in
1623              normal positions. */
1624           v = VariantNoCastle;
1625           break;
1626         case 5:
1627         case 7:
1628         case 8:
1629         case 10:
1630         case 11:
1631         case 12:
1632         case 13:
1633         case 14:
1634         case 15:
1635         case 18:
1636         case 19:
1637           /* Castling legal iff K & R start in normal positions */
1638           v = VariantNormal;
1639           break;
1640         case 6:
1641         case 20:
1642         case 21:
1643           /* Special wilds for position setup; unclear what to do here */
1644           v = VariantLoadable;
1645           break;
1646         case 9:
1647           /* Bizarre ICC game */
1648           v = VariantTwoKings;
1649           break;
1650         case 16:
1651           v = VariantKriegspiel;
1652           break;
1653         case 17:
1654           v = VariantLosers;
1655           break;
1656         case 22:
1657           v = VariantFischeRandom;
1658           break;
1659         case 23:
1660           v = VariantCrazyhouse;
1661           break;
1662         case 24:
1663           v = VariantBughouse;
1664           break;
1665         case 25:
1666           v = Variant3Check;
1667           break;
1668         case 26:
1669           /* Not quite the same as FICS suicide! */
1670           v = VariantGiveaway;
1671           break;
1672         case 27:
1673           v = VariantAtomic;
1674           break;
1675         case 28:
1676           v = VariantShatranj;
1677           break;
1678
1679         /* Temporary names for future ICC types.  The name *will* change in 
1680            the next xboard/WinBoard release after ICC defines it. */
1681         case 29:
1682           v = Variant29;
1683           break;
1684         case 30:
1685           v = Variant30;
1686           break;
1687         case 31:
1688           v = Variant31;
1689           break;
1690         case 32:
1691           v = Variant32;
1692           break;
1693         case 33:
1694           v = Variant33;
1695           break;
1696         case 34:
1697           v = Variant34;
1698           break;
1699         case 35:
1700           v = Variant35;
1701           break;
1702         case 36:
1703           v = Variant36;
1704           break;
1705         case 37:
1706           v = VariantShogi;
1707           break;
1708         case 38:
1709           v = VariantXiangqi;
1710           break;
1711         case 39:
1712           v = VariantCourier;
1713           break;
1714         case 40:
1715           v = VariantGothic;
1716           break;
1717         case 41:
1718           v = VariantCapablanca;
1719           break;
1720         case 42:
1721           v = VariantKnightmate;
1722           break;
1723         case 43:
1724           v = VariantFairy;
1725           break;
1726         case 44:
1727           v = VariantCylinder;
1728           break;
1729         case 45:
1730           v = VariantFalcon;
1731           break;
1732         case 46:
1733           v = VariantCapaRandom;
1734           break;
1735         case 47:
1736           v = VariantBerolina;
1737           break;
1738         case 48:
1739           v = VariantJanus;
1740           break;
1741         case 49:
1742           v = VariantSuper;
1743           break;
1744         case 50:
1745           v = VariantGreat;
1746           break;
1747         case -1:
1748           /* Found "wild" or "w" in the string but no number;
1749              must assume it's normal chess. */
1750           v = VariantNormal;
1751           break;
1752         default:
1753           sprintf(buf, _("Unknown wild type %d"), wnum);
1754           DisplayError(buf, 0);
1755           v = VariantUnknown;
1756           break;
1757         }
1758       }
1759     }
1760     if (appData.debugMode) {
1761       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1762               e, wnum, VariantName(v));
1763     }
1764     return v;
1765 }
1766
1767 static int leftover_start = 0, leftover_len = 0;
1768 char star_match[STAR_MATCH_N][MSG_SIZ];
1769
1770 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1771    advance *index beyond it, and set leftover_start to the new value of
1772    *index; else return FALSE.  If pattern contains the character '*', it
1773    matches any sequence of characters not containing '\r', '\n', or the
1774    character following the '*' (if any), and the matched sequence(s) are
1775    copied into star_match.
1776    */
1777 int
1778 looking_at(buf, index, pattern)
1779      char *buf;
1780      int *index;
1781      char *pattern;
1782 {
1783     char *bufp = &buf[*index], *patternp = pattern;
1784     int star_count = 0;
1785     char *matchp = star_match[0];
1786     
1787     for (;;) {
1788         if (*patternp == NULLCHAR) {
1789             *index = leftover_start = bufp - buf;
1790             *matchp = NULLCHAR;
1791             return TRUE;
1792         }
1793         if (*bufp == NULLCHAR) return FALSE;
1794         if (*patternp == '*') {
1795             if (*bufp == *(patternp + 1)) {
1796                 *matchp = NULLCHAR;
1797                 matchp = star_match[++star_count];
1798                 patternp += 2;
1799                 bufp++;
1800                 continue;
1801             } else if (*bufp == '\n' || *bufp == '\r') {
1802                 patternp++;
1803                 if (*patternp == NULLCHAR)
1804                   continue;
1805                 else
1806                   return FALSE;
1807             } else {
1808                 *matchp++ = *bufp++;
1809                 continue;
1810             }
1811         }
1812         if (*patternp != *bufp) return FALSE;
1813         patternp++;
1814         bufp++;
1815     }
1816 }
1817
1818 void
1819 SendToPlayer(data, length)
1820      char *data;
1821      int length;
1822 {
1823     int error, outCount;
1824     outCount = OutputToProcess(NoProc, data, length, &error);
1825     if (outCount < length) {
1826         DisplayFatalError(_("Error writing to display"), error, 1);
1827     }
1828 }
1829
1830 void
1831 PackHolding(packed, holding)
1832      char packed[];
1833      char *holding;
1834 {
1835     char *p = holding;
1836     char *q = packed;
1837     int runlength = 0;
1838     int curr = 9999;
1839     do {
1840         if (*p == curr) {
1841             runlength++;
1842         } else {
1843             switch (runlength) {
1844               case 0:
1845                 break;
1846               case 1:
1847                 *q++ = curr;
1848                 break;
1849               case 2:
1850                 *q++ = curr;
1851                 *q++ = curr;
1852                 break;
1853               default:
1854                 sprintf(q, "%d", runlength);
1855                 while (*q) q++;
1856                 *q++ = curr;
1857                 break;
1858             }
1859             runlength = 1;
1860             curr = *p;
1861         }
1862     } while (*p++);
1863     *q = NULLCHAR;
1864 }
1865
1866 /* Telnet protocol requests from the front end */
1867 void
1868 TelnetRequest(ddww, option)
1869      unsigned char ddww, option;
1870 {
1871     unsigned char msg[3];
1872     int outCount, outError;
1873
1874     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1875
1876     if (appData.debugMode) {
1877         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1878         switch (ddww) {
1879           case TN_DO:
1880             ddwwStr = "DO";
1881             break;
1882           case TN_DONT:
1883             ddwwStr = "DONT";
1884             break;
1885           case TN_WILL:
1886             ddwwStr = "WILL";
1887             break;
1888           case TN_WONT:
1889             ddwwStr = "WONT";
1890             break;
1891           default:
1892             ddwwStr = buf1;
1893             sprintf(buf1, "%d", ddww);
1894             break;
1895         }
1896         switch (option) {
1897           case TN_ECHO:
1898             optionStr = "ECHO";
1899             break;
1900           default:
1901             optionStr = buf2;
1902             sprintf(buf2, "%d", option);
1903             break;
1904         }
1905         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1906     }
1907     msg[0] = TN_IAC;
1908     msg[1] = ddww;
1909     msg[2] = option;
1910     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1911     if (outCount < 3) {
1912         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1913     }
1914 }
1915
1916 void
1917 DoEcho()
1918 {
1919     if (!appData.icsActive) return;
1920     TelnetRequest(TN_DO, TN_ECHO);
1921 }
1922
1923 void
1924 DontEcho()
1925 {
1926     if (!appData.icsActive) return;
1927     TelnetRequest(TN_DONT, TN_ECHO);
1928 }
1929
1930 void
1931 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1932 {
1933     /* put the holdings sent to us by the server on the board holdings area */
1934     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935     char p;
1936     ChessSquare piece;
1937
1938     if(gameInfo.holdingsWidth < 2)  return;
1939     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1940         return; // prevent overwriting by pre-board holdings
1941
1942     if( (int)lowestPiece >= BlackPawn ) {
1943         holdingsColumn = 0;
1944         countsColumn = 1;
1945         holdingsStartRow = BOARD_HEIGHT-1;
1946         direction = -1;
1947     } else {
1948         holdingsColumn = BOARD_WIDTH-1;
1949         countsColumn = BOARD_WIDTH-2;
1950         holdingsStartRow = 0;
1951         direction = 1;
1952     }
1953
1954     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1955         board[i][holdingsColumn] = EmptySquare;
1956         board[i][countsColumn]   = (ChessSquare) 0;
1957     }
1958     while( (p=*holdings++) != NULLCHAR ) {
1959         piece = CharToPiece( ToUpper(p) );
1960         if(piece == EmptySquare) continue;
1961         /*j = (int) piece - (int) WhitePawn;*/
1962         j = PieceToNumber(piece);
1963         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1964         if(j < 0) continue;               /* should not happen */
1965         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1966         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1967         board[holdingsStartRow+j*direction][countsColumn]++;
1968     }
1969 }
1970
1971
1972 void
1973 VariantSwitch(Board board, VariantClass newVariant)
1974 {
1975    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976    Board oldBoard;
1977
1978    startedFromPositionFile = FALSE;
1979    if(gameInfo.variant == newVariant) return;
1980
1981    /* [HGM] This routine is called each time an assignment is made to
1982     * gameInfo.variant during a game, to make sure the board sizes
1983     * are set to match the new variant. If that means adding or deleting
1984     * holdings, we shift the playing board accordingly
1985     * This kludge is needed because in ICS observe mode, we get boards
1986     * of an ongoing game without knowing the variant, and learn about the
1987     * latter only later. This can be because of the move list we requested,
1988     * in which case the game history is refilled from the beginning anyway,
1989     * but also when receiving holdings of a crazyhouse game. In the latter
1990     * case we want to add those holdings to the already received position.
1991     */
1992
1993    
1994    if (appData.debugMode) {
1995      fprintf(debugFP, "Switch board from %s to %s\n",
1996              VariantName(gameInfo.variant), VariantName(newVariant));
1997      setbuf(debugFP, NULL);
1998    }
1999    shuffleOpenings = 0;       /* [HGM] shuffle */
2000    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001    switch(newVariant) 
2002      {
2003      case VariantShogi:
2004        newWidth = 9;  newHeight = 9;
2005        gameInfo.holdingsSize = 7;
2006      case VariantBughouse:
2007      case VariantCrazyhouse:
2008        newHoldingsWidth = 2; break;
2009      case VariantGreat:
2010        newWidth = 10;
2011      case VariantSuper:
2012        newHoldingsWidth = 2;
2013        gameInfo.holdingsSize = 8;
2014        break;
2015      case VariantGothic:
2016      case VariantCapablanca:
2017      case VariantCapaRandom:
2018        newWidth = 10;
2019      default:
2020        newHoldingsWidth = gameInfo.holdingsSize = 0;
2021      };
2022    
2023    if(newWidth  != gameInfo.boardWidth  ||
2024       newHeight != gameInfo.boardHeight ||
2025       newHoldingsWidth != gameInfo.holdingsWidth ) {
2026      
2027      /* shift position to new playing area, if needed */
2028      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2029        for(i=0; i<BOARD_HEIGHT; i++) 
2030          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2031            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2032              board[i][j];
2033        for(i=0; i<newHeight; i++) {
2034          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2035          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2036        }
2037      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2038        for(i=0; i<BOARD_HEIGHT; i++)
2039          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2040            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041              board[i][j];
2042      }
2043      gameInfo.boardWidth  = newWidth;
2044      gameInfo.boardHeight = newHeight;
2045      gameInfo.holdingsWidth = newHoldingsWidth;
2046      gameInfo.variant = newVariant;
2047      InitDrawingSizes(-2, 0);
2048    } else gameInfo.variant = newVariant;
2049    CopyBoard(oldBoard, board);   // remember correctly formatted board
2050      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2051    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 }
2053
2054 static int loggedOn = FALSE;
2055
2056 /*-- Game start info cache: --*/
2057 int gs_gamenum;
2058 char gs_kind[MSG_SIZ];
2059 static char player1Name[128] = "";
2060 static char player2Name[128] = "";
2061 static char cont_seq[] = "\n\\   ";
2062 static int player1Rating = -1;
2063 static int player2Rating = -1;
2064 /*----------------------------*/
2065
2066 ColorClass curColor = ColorNormal;
2067 int suppressKibitz = 0;
2068
2069 // [HGM] seekgraph
2070 Boolean soughtPending = FALSE;
2071 Boolean seekGraphUp;
2072 #define MAX_SEEK_ADS 200
2073 #define SQUARE 0x80
2074 char *seekAdList[MAX_SEEK_ADS];
2075 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2076 float tcList[MAX_SEEK_ADS];
2077 char colorList[MAX_SEEK_ADS];
2078 int nrOfSeekAds = 0;
2079 int minRating = 1010, maxRating = 2800;
2080 int hMargin = 10, vMargin = 20, h, w;
2081 extern int squareSize, lineGap;
2082
2083 void
2084 PlotSeekAd(int i)
2085 {
2086         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2087         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2088         if(r < minRating+100 && r >=0 ) r = minRating+100;
2089         if(r > maxRating) r = maxRating;
2090         if(tc < 1.) tc = 1.;
2091         if(tc > 95.) tc = 95.;
2092         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2093         y = ((double)r - minRating)/(maxRating - minRating)
2094             * (h-vMargin-squareSize/8-1) + vMargin;
2095         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2096         if(strstr(seekAdList[i], " u ")) color = 1;
2097         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2098            !strstr(seekAdList[i], "bullet") &&
2099            !strstr(seekAdList[i], "blitz") &&
2100            !strstr(seekAdList[i], "standard") ) color = 2;
2101         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2102         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2103 }
2104
2105 void
2106 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2107 {
2108         char buf[MSG_SIZ], *ext = "";
2109         VariantClass v = StringToVariant(type);
2110         if(strstr(type, "wild")) {
2111             ext = type + 4; // append wild number
2112             if(v == VariantFischeRandom) type = "chess960"; else
2113             if(v == VariantLoadable) type = "setup"; else
2114             type = VariantName(v);
2115         }
2116         sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2117         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2118             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2119             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2120             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2121             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2122             seekNrList[nrOfSeekAds] = nr;
2123             zList[nrOfSeekAds] = 0;
2124             seekAdList[nrOfSeekAds++] = StrSave(buf);
2125             if(plot) PlotSeekAd(nrOfSeekAds-1);
2126         }
2127 }
2128
2129 void
2130 EraseSeekDot(int i)
2131 {
2132     int x = xList[i], y = yList[i], d=squareSize/4, k;
2133     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2134     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2135     // now replot every dot that overlapped
2136     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2137         int xx = xList[k], yy = yList[k];
2138         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2139             DrawSeekDot(xx, yy, colorList[k]);
2140     }
2141 }
2142
2143 void
2144 RemoveSeekAd(int nr)
2145 {
2146         int i;
2147         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2148             EraseSeekDot(i);
2149             if(seekAdList[i]) free(seekAdList[i]);
2150             seekAdList[i] = seekAdList[--nrOfSeekAds];
2151             seekNrList[i] = seekNrList[nrOfSeekAds];
2152             ratingList[i] = ratingList[nrOfSeekAds];
2153             colorList[i]  = colorList[nrOfSeekAds];
2154             tcList[i] = tcList[nrOfSeekAds];
2155             xList[i]  = xList[nrOfSeekAds];
2156             yList[i]  = yList[nrOfSeekAds];
2157             zList[i]  = zList[nrOfSeekAds];
2158             seekAdList[nrOfSeekAds] = NULL;
2159             break;
2160         }
2161 }
2162
2163 Boolean
2164 MatchSoughtLine(char *line)
2165 {
2166     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2167     int nr, base, inc, u=0; char dummy;
2168
2169     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2170        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2171        (u=1) &&
2172        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2173         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2174         // match: compact and save the line
2175         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2176         return TRUE;
2177     }
2178     return FALSE;
2179 }
2180
2181 int
2182 DrawSeekGraph()
2183 {
2184     if(!seekGraphUp) return FALSE;
2185     int i;
2186     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2187     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2188
2189     DrawSeekBackground(0, 0, w, h);
2190     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2191     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2192     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2193         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2194         yy = h-1-yy;
2195         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196         if(i%500 == 0) {
2197             char buf[MSG_SIZ];
2198             sprintf(buf, "%d", i);
2199             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200         }
2201     }
2202     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2203     for(i=1; i<100; i+=(i<10?1:5)) {
2204         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2205         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2206         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2207             char buf[MSG_SIZ];
2208             sprintf(buf, "%d", i);
2209             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210         }
2211     }
2212     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2213     return TRUE;
2214 }
2215
2216 int SeekGraphClick(ClickType click, int x, int y, int moving)
2217 {
2218     static int lastDown = 0, displayed = 0, lastSecond;
2219     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2220         if(click == Release || moving) return FALSE;
2221         nrOfSeekAds = 0;
2222         soughtPending = TRUE;
2223         SendToICS(ics_prefix);
2224         SendToICS("sought\n"); // should this be "sought all"?
2225     } else { // issue challenge based on clicked ad
2226         int dist = 10000; int i, closest = 0, second = 0;
2227         for(i=0; i<nrOfSeekAds; i++) {
2228             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2229             if(d < dist) { dist = d; closest = i; }
2230             second += (d - zList[i] < 120); // count in-range ads
2231             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2232         }
2233         if(dist < 120) {
2234             char buf[MSG_SIZ];
2235             second = (second > 1);
2236             if(displayed != closest || second != lastSecond) {
2237                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2238                 lastSecond = second; displayed = closest;
2239             }
2240             if(click == Press) {
2241                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2242                 lastDown = closest;
2243                 return TRUE;
2244             } // on press 'hit', only show info
2245             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2246             sprintf(buf, "play %d\n", seekNrList[closest]);
2247             SendToICS(ics_prefix);
2248             SendToICS(buf);
2249             return TRUE; // let incoming board of started game pop down the graph
2250         } else if(click == Release) { // release 'miss' is ignored
2251             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2252             if(moving == 2) { // right up-click
2253                 nrOfSeekAds = 0; // refresh graph
2254                 soughtPending = TRUE;
2255                 SendToICS(ics_prefix);
2256                 SendToICS("sought\n"); // should this be "sought all"?
2257             }
2258             return TRUE;
2259         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2260         // press miss or release hit 'pop down' seek graph
2261         seekGraphUp = FALSE;
2262         DrawPosition(TRUE, NULL);
2263     }
2264     return TRUE;
2265 }
2266
2267 void
2268 read_from_ics(isr, closure, data, count, error)
2269      InputSourceRef isr;
2270      VOIDSTAR closure;
2271      char *data;
2272      int count;
2273      int error;
2274 {
2275 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2276 #define STARTED_NONE 0
2277 #define STARTED_MOVES 1
2278 #define STARTED_BOARD 2
2279 #define STARTED_OBSERVE 3
2280 #define STARTED_HOLDINGS 4
2281 #define STARTED_CHATTER 5
2282 #define STARTED_COMMENT 6
2283 #define STARTED_MOVES_NOHIDE 7
2284     
2285     static int started = STARTED_NONE;
2286     static char parse[20000];
2287     static int parse_pos = 0;
2288     static char buf[BUF_SIZE + 1];
2289     static int firstTime = TRUE, intfSet = FALSE;
2290     static ColorClass prevColor = ColorNormal;
2291     static int savingComment = FALSE;
2292     static int cmatch = 0; // continuation sequence match
2293     char *bp;
2294     char str[500];
2295     int i, oldi;
2296     int buf_len;
2297     int next_out;
2298     int tkind;
2299     int backup;    /* [DM] For zippy color lines */
2300     char *p;
2301     char talker[MSG_SIZ]; // [HGM] chat
2302     int channel;
2303
2304     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2305
2306     if (appData.debugMode) {
2307       if (!error) {
2308         fprintf(debugFP, "<ICS: ");
2309         show_bytes(debugFP, data, count);
2310         fprintf(debugFP, "\n");
2311       }
2312     }
2313
2314     if (appData.debugMode) { int f = forwardMostMove;
2315         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2316                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2317                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2318     }
2319     if (count > 0) {
2320         /* If last read ended with a partial line that we couldn't parse,
2321            prepend it to the new read and try again. */
2322         if (leftover_len > 0) {
2323             for (i=0; i<leftover_len; i++)
2324               buf[i] = buf[leftover_start + i];
2325         }
2326
2327     /* copy new characters into the buffer */
2328     bp = buf + leftover_len;
2329     buf_len=leftover_len;
2330     for (i=0; i<count; i++)
2331     {
2332         // ignore these
2333         if (data[i] == '\r')
2334             continue;
2335
2336         // join lines split by ICS?
2337         if (!appData.noJoin)
2338         {
2339             /*
2340                 Joining just consists of finding matches against the
2341                 continuation sequence, and discarding that sequence
2342                 if found instead of copying it.  So, until a match
2343                 fails, there's nothing to do since it might be the
2344                 complete sequence, and thus, something we don't want
2345                 copied.
2346             */
2347             if (data[i] == cont_seq[cmatch])
2348             {
2349                 cmatch++;
2350                 if (cmatch == strlen(cont_seq))
2351                 {
2352                     cmatch = 0; // complete match.  just reset the counter
2353
2354                     /*
2355                         it's possible for the ICS to not include the space
2356                         at the end of the last word, making our [correct]
2357                         join operation fuse two separate words.  the server
2358                         does this when the space occurs at the width setting.
2359                     */
2360                     if (!buf_len || buf[buf_len-1] != ' ')
2361                     {
2362                         *bp++ = ' ';
2363                         buf_len++;
2364                     }
2365                 }
2366                 continue;
2367             }
2368             else if (cmatch)
2369             {
2370                 /*
2371                     match failed, so we have to copy what matched before
2372                     falling through and copying this character.  In reality,
2373                     this will only ever be just the newline character, but
2374                     it doesn't hurt to be precise.
2375                 */
2376                 strncpy(bp, cont_seq, cmatch);
2377                 bp += cmatch;
2378                 buf_len += cmatch;
2379                 cmatch = 0;
2380             }
2381         }
2382
2383         // copy this char
2384         *bp++ = data[i];
2385         buf_len++;
2386     }
2387
2388         buf[buf_len] = NULLCHAR;
2389 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2390         next_out = 0;
2391         leftover_start = 0;
2392         
2393         i = 0;
2394         while (i < buf_len) {
2395             /* Deal with part of the TELNET option negotiation
2396                protocol.  We refuse to do anything beyond the
2397                defaults, except that we allow the WILL ECHO option,
2398                which ICS uses to turn off password echoing when we are
2399                directly connected to it.  We reject this option
2400                if localLineEditing mode is on (always on in xboard)
2401                and we are talking to port 23, which might be a real
2402                telnet server that will try to keep WILL ECHO on permanently.
2403              */
2404             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2405                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2406                 unsigned char option;
2407                 oldi = i;
2408                 switch ((unsigned char) buf[++i]) {
2409                   case TN_WILL:
2410                     if (appData.debugMode)
2411                       fprintf(debugFP, "\n<WILL ");
2412                     switch (option = (unsigned char) buf[++i]) {
2413                       case TN_ECHO:
2414                         if (appData.debugMode)
2415                           fprintf(debugFP, "ECHO ");
2416                         /* Reply only if this is a change, according
2417                            to the protocol rules. */
2418                         if (remoteEchoOption) break;
2419                         if (appData.localLineEditing &&
2420                             atoi(appData.icsPort) == TN_PORT) {
2421                             TelnetRequest(TN_DONT, TN_ECHO);
2422                         } else {
2423                             EchoOff();
2424                             TelnetRequest(TN_DO, TN_ECHO);
2425                             remoteEchoOption = TRUE;
2426                         }
2427                         break;
2428                       default:
2429                         if (appData.debugMode)
2430                           fprintf(debugFP, "%d ", option);
2431                         /* Whatever this is, we don't want it. */
2432                         TelnetRequest(TN_DONT, option);
2433                         break;
2434                     }
2435                     break;
2436                   case TN_WONT:
2437                     if (appData.debugMode)
2438                       fprintf(debugFP, "\n<WONT ");
2439                     switch (option = (unsigned char) buf[++i]) {
2440                       case TN_ECHO:
2441                         if (appData.debugMode)
2442                           fprintf(debugFP, "ECHO ");
2443                         /* Reply only if this is a change, according
2444                            to the protocol rules. */
2445                         if (!remoteEchoOption) break;
2446                         EchoOn();
2447                         TelnetRequest(TN_DONT, TN_ECHO);
2448                         remoteEchoOption = FALSE;
2449                         break;
2450                       default:
2451                         if (appData.debugMode)
2452                           fprintf(debugFP, "%d ", (unsigned char) option);
2453                         /* Whatever this is, it must already be turned
2454                            off, because we never agree to turn on
2455                            anything non-default, so according to the
2456                            protocol rules, we don't reply. */
2457                         break;
2458                     }
2459                     break;
2460                   case TN_DO:
2461                     if (appData.debugMode)
2462                       fprintf(debugFP, "\n<DO ");
2463                     switch (option = (unsigned char) buf[++i]) {
2464                       default:
2465                         /* Whatever this is, we refuse to do it. */
2466                         if (appData.debugMode)
2467                           fprintf(debugFP, "%d ", option);
2468                         TelnetRequest(TN_WONT, option);
2469                         break;
2470                     }
2471                     break;
2472                   case TN_DONT:
2473                     if (appData.debugMode)
2474                       fprintf(debugFP, "\n<DONT ");
2475                     switch (option = (unsigned char) buf[++i]) {
2476                       default:
2477                         if (appData.debugMode)
2478                           fprintf(debugFP, "%d ", option);
2479                         /* Whatever this is, we are already not doing
2480                            it, because we never agree to do anything
2481                            non-default, so according to the protocol
2482                            rules, we don't reply. */
2483                         break;
2484                     }
2485                     break;
2486                   case TN_IAC:
2487                     if (appData.debugMode)
2488                       fprintf(debugFP, "\n<IAC ");
2489                     /* Doubled IAC; pass it through */
2490                     i--;
2491                     break;
2492                   default:
2493                     if (appData.debugMode)
2494                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2495                     /* Drop all other telnet commands on the floor */
2496                     break;
2497                 }
2498                 if (oldi > next_out)
2499                   SendToPlayer(&buf[next_out], oldi - next_out);
2500                 if (++i > next_out)
2501                   next_out = i;
2502                 continue;
2503             }
2504                 
2505             /* OK, this at least will *usually* work */
2506             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507                 loggedOn = TRUE;
2508             }
2509             
2510             if (loggedOn && !intfSet) {
2511                 if (ics_type == ICS_ICC) {
2512                   sprintf(str,
2513                           "/set-quietly interface %s\n/set-quietly style 12\n",
2514                           programVersion);
2515                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2516                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2517                 } else if (ics_type == ICS_CHESSNET) {
2518                   sprintf(str, "/style 12\n");
2519                 } else {
2520                   strcpy(str, "alias $ @\n$set interface ");
2521                   strcat(str, programVersion);
2522                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2523                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2524                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2525 #ifdef WIN32
2526                   strcat(str, "$iset nohighlight 1\n");
2527 #endif
2528                   strcat(str, "$iset lock 1\n$style 12\n");
2529                 }
2530                 SendToICS(str);
2531                 NotifyFrontendLogin();
2532                 intfSet = TRUE;
2533             }
2534
2535             if (started == STARTED_COMMENT) {
2536                 /* Accumulate characters in comment */
2537                 parse[parse_pos++] = buf[i];
2538                 if (buf[i] == '\n') {
2539                     parse[parse_pos] = NULLCHAR;
2540                     if(chattingPartner>=0) {
2541                         char mess[MSG_SIZ];
2542                         sprintf(mess, "%s%s", talker, parse);
2543                         OutputChatMessage(chattingPartner, mess);
2544                         chattingPartner = -1;
2545                         next_out = i+1; // [HGM] suppress printing in ICS window
2546                     } else
2547                     if(!suppressKibitz) // [HGM] kibitz
2548                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2549                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2550                         int nrDigit = 0, nrAlph = 0, j;
2551                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2552                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2553                         parse[parse_pos] = NULLCHAR;
2554                         // try to be smart: if it does not look like search info, it should go to
2555                         // ICS interaction window after all, not to engine-output window.
2556                         for(j=0; j<parse_pos; j++) { // count letters and digits
2557                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2558                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2559                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2560                         }
2561                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2562                             int depth=0; float score;
2563                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2564                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2565                                 pvInfoList[forwardMostMove-1].depth = depth;
2566                                 pvInfoList[forwardMostMove-1].score = 100*score;
2567                             }
2568                             OutputKibitz(suppressKibitz, parse);
2569                         } else {
2570                             char tmp[MSG_SIZ];
2571                             sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2572                             SendToPlayer(tmp, strlen(tmp));
2573                         }
2574                         next_out = i+1; // [HGM] suppress printing in ICS window
2575                     }
2576                     started = STARTED_NONE;
2577                 } else {
2578                     /* Don't match patterns against characters in comment */
2579                     i++;
2580                     continue;
2581                 }
2582             }
2583             if (started == STARTED_CHATTER) {
2584                 if (buf[i] != '\n') {
2585                     /* Don't match patterns against characters in chatter */
2586                     i++;
2587                     continue;
2588                 }
2589                 started = STARTED_NONE;
2590                 if(suppressKibitz) next_out = i+1;
2591             }
2592
2593             /* Kludge to deal with rcmd protocol */
2594             if (firstTime && looking_at(buf, &i, "\001*")) {
2595                 DisplayFatalError(&buf[1], 0, 1);
2596                 continue;
2597             } else {
2598                 firstTime = FALSE;
2599             }
2600
2601             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2602                 ics_type = ICS_ICC;
2603                 ics_prefix = "/";
2604                 if (appData.debugMode)
2605                   fprintf(debugFP, "ics_type %d\n", ics_type);
2606                 continue;
2607             }
2608             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2609                 ics_type = ICS_FICS;
2610                 ics_prefix = "$";
2611                 if (appData.debugMode)
2612                   fprintf(debugFP, "ics_type %d\n", ics_type);
2613                 continue;
2614             }
2615             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2616                 ics_type = ICS_CHESSNET;
2617                 ics_prefix = "/";
2618                 if (appData.debugMode)
2619                   fprintf(debugFP, "ics_type %d\n", ics_type);
2620                 continue;
2621             }
2622
2623             if (!loggedOn &&
2624                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2625                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2626                  looking_at(buf, &i, "will be \"*\""))) {
2627               strcpy(ics_handle, star_match[0]);
2628               continue;
2629             }
2630
2631             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2632               char buf[MSG_SIZ];
2633               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2634               DisplayIcsInteractionTitle(buf);
2635               have_set_title = TRUE;
2636             }
2637
2638             /* skip finger notes */
2639             if (started == STARTED_NONE &&
2640                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2641                  (buf[i] == '1' && buf[i+1] == '0')) &&
2642                 buf[i+2] == ':' && buf[i+3] == ' ') {
2643               started = STARTED_CHATTER;
2644               i += 3;
2645               continue;
2646             }
2647
2648             oldi = i;
2649             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2650             if(appData.seekGraph) {
2651                 if(soughtPending && MatchSoughtLine(buf+i)) {
2652                     i = strstr(buf+i, "rated") - buf;
2653                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2654                     next_out = leftover_start = i;
2655                     started = STARTED_CHATTER;
2656                     suppressKibitz = TRUE;
2657                     continue;
2658                 }
2659                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2660                         && looking_at(buf, &i, "* ads displayed")) {
2661                     soughtPending = FALSE;
2662                     seekGraphUp = TRUE;
2663                     DrawSeekGraph();
2664                     continue;
2665                 }
2666                 if(appData.autoRefresh) {
2667                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2668                         int s = (ics_type == ICS_ICC); // ICC format differs
2669                         if(seekGraphUp)
2670                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]), 
2671                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2672                         looking_at(buf, &i, "*% "); // eat prompt
2673                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2674                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2675                         next_out = i; // suppress
2676                         continue;
2677                     }
2678                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2679                         char *p = star_match[0];
2680                         while(*p) {
2681                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2682                             while(*p && *p++ != ' '); // next
2683                         }
2684                         looking_at(buf, &i, "*% "); // eat prompt
2685                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2686                         next_out = i;
2687                         continue;
2688                     }
2689                 }
2690             }
2691
2692             /* skip formula vars */
2693             if (started == STARTED_NONE &&
2694                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2695               started = STARTED_CHATTER;
2696               i += 3;
2697               continue;
2698             }
2699
2700             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2701             if (appData.autoKibitz && started == STARTED_NONE && 
2702                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2703                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2704                 if(looking_at(buf, &i, "* kibitzes: ") &&
2705                    (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
2706                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2707                         suppressKibitz = TRUE;
2708                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2709                         next_out = i;
2710                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2711                                 && (gameMode == IcsPlayingWhite)) ||
2712                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2713                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2714                             started = STARTED_CHATTER; // own kibitz we simply discard
2715                         else {
2716                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2717                             parse_pos = 0; parse[0] = NULLCHAR;
2718                             savingComment = TRUE;
2719                             suppressKibitz = gameMode != IcsObserving ? 2 :
2720                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2721                         } 
2722                         continue;
2723                 } else
2724                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2725                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2726                          && atoi(star_match[0])) {
2727                     // suppress the acknowledgements of our own autoKibitz
2728                     char *p;
2729                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2730                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2731                     SendToPlayer(star_match[0], strlen(star_match[0]));
2732                     if(looking_at(buf, &i, "*% ")) // eat prompt
2733                         suppressKibitz = FALSE;
2734                     next_out = i;
2735                     continue;
2736                 }
2737             } // [HGM] kibitz: end of patch
2738
2739             // [HGM] chat: intercept tells by users for which we have an open chat window
2740             channel = -1;
2741             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
2742                                            looking_at(buf, &i, "* whispers:") ||
2743                                            looking_at(buf, &i, "* shouts:") ||
2744                                            looking_at(buf, &i, "* c-shouts:") ||
2745                                            looking_at(buf, &i, "--> * ") ||
2746                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2747                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2748                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2749                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2750                 int p;
2751                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2752                 chattingPartner = -1;
2753
2754                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2755                 for(p=0; p<MAX_CHAT; p++) {
2756                     if(channel == atoi(chatPartner[p])) {
2757                     talker[0] = '['; strcat(talker, "] ");
2758                     chattingPartner = p; break;
2759                     }
2760                 } else
2761                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2762                 for(p=0; p<MAX_CHAT; p++) {
2763                     if(!strcmp("whispers", chatPartner[p])) {
2764                         talker[0] = '['; strcat(talker, "] ");
2765                         chattingPartner = p; break;
2766                     }
2767                 } else
2768                 if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
2769                 for(p=0; p<MAX_CHAT; p++) {
2770                     if(!strcmp("shouts", chatPartner[p])) {
2771                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); }
2772                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); }
2773                         else { talker[0] = '['; strcat(talker, "] "); }
2774                         chattingPartner = p; break;
2775                     }
2776                 }
2777                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2778                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2779                     talker[0] = 0;
2780                     chattingPartner = p; break;
2781                 }
2782                 if(chattingPartner<0) i = oldi; else {
2783                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2784                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2785                     started = STARTED_COMMENT;
2786                     parse_pos = 0; parse[0] = NULLCHAR;
2787                     savingComment = 3 + chattingPartner; // counts as TRUE
2788                     suppressKibitz = TRUE;
2789                     continue;
2790                 }
2791             } // [HGM] chat: end of patch
2792
2793             if (appData.zippyTalk || appData.zippyPlay) {
2794                 /* [DM] Backup address for color zippy lines */
2795                 backup = i;
2796 #if ZIPPY
2797        #ifdef WIN32
2798                if (loggedOn == TRUE)
2799                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2800                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2801        #else
2802                 if (ZippyControl(buf, &i) ||
2803                     ZippyConverse(buf, &i) ||
2804                     (appData.zippyPlay && ZippyMatch(buf, &i))) {
2805                       loggedOn = TRUE;
2806                       if (!appData.colorize) continue;
2807                 }
2808        #endif
2809 #endif
2810             } // [DM] 'else { ' deleted
2811                 if (
2812                     /* Regular tells and says */
2813                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2814                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2815                     looking_at(buf, &i, "* says: ") ||
2816                     /* Don't color "message" or "messages" output */
2817                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2818                     looking_at(buf, &i, "*. * at *:*: ") ||
2819                     looking_at(buf, &i, "--* (*:*): ") ||
2820                     /* Message notifications (same color as tells) */
2821                     looking_at(buf, &i, "* has left a message ") ||
2822                     looking_at(buf, &i, "* just sent you a message:\n") ||
2823                     /* Whispers and kibitzes */
2824                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2825                     looking_at(buf, &i, "* kibitzes: ") ||
2826                     /* Channel tells */
2827                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2828
2829                   if (tkind == 1 && strchr(star_match[0], ':')) {
2830                       /* Avoid "tells you:" spoofs in channels */
2831                      tkind = 3;
2832                   }
2833                   if (star_match[0][0] == NULLCHAR ||
2834                       strchr(star_match[0], ' ') ||
2835                       (tkind == 3 && strchr(star_match[1], ' '))) {
2836                     /* Reject bogus matches */
2837                     i = oldi;
2838                   } else {
2839                     if (appData.colorize) {
2840                       if (oldi > next_out) {
2841                         SendToPlayer(&buf[next_out], oldi - next_out);
2842                         next_out = oldi;
2843                       }
2844                       switch (tkind) {
2845                       case 1:
2846                         Colorize(ColorTell, FALSE);
2847                         curColor = ColorTell;
2848                         break;
2849                       case 2:
2850                         Colorize(ColorKibitz, FALSE);
2851                         curColor = ColorKibitz;
2852                         break;
2853                       case 3:
2854                         p = strrchr(star_match[1], '(');
2855                         if (p == NULL) {
2856                           p = star_match[1];
2857                         } else {
2858                           p++;
2859                         }
2860                         if (atoi(p) == 1) {
2861                           Colorize(ColorChannel1, FALSE);
2862                           curColor = ColorChannel1;
2863                         } else {
2864                           Colorize(ColorChannel, FALSE);
2865                           curColor = ColorChannel;
2866                         }
2867                         break;
2868                       case 5:
2869                         curColor = ColorNormal;
2870                         break;
2871                       }
2872                     }
2873                     if (started == STARTED_NONE && appData.autoComment &&
2874                         (gameMode == IcsObserving ||
2875                          gameMode == IcsPlayingWhite ||
2876                          gameMode == IcsPlayingBlack)) {
2877                       parse_pos = i - oldi;
2878                       memcpy(parse, &buf[oldi], parse_pos);
2879                       parse[parse_pos] = NULLCHAR;
2880                       started = STARTED_COMMENT;
2881                       savingComment = TRUE;
2882                     } else {
2883                       started = STARTED_CHATTER;
2884                       savingComment = FALSE;
2885                     }
2886                     loggedOn = TRUE;
2887                     continue;
2888                   }
2889                 }
2890
2891                 if (looking_at(buf, &i, "* s-shouts: ") ||
2892                     looking_at(buf, &i, "* c-shouts: ")) {
2893                     if (appData.colorize) {
2894                         if (oldi > next_out) {
2895                             SendToPlayer(&buf[next_out], oldi - next_out);
2896                             next_out = oldi;
2897                         }
2898                         Colorize(ColorSShout, FALSE);
2899                         curColor = ColorSShout;
2900                     }
2901                     loggedOn = TRUE;
2902                     started = STARTED_CHATTER;
2903                     continue;
2904                 }
2905
2906                 if (looking_at(buf, &i, "--->")) {
2907                     loggedOn = TRUE;
2908                     continue;
2909                 }
2910
2911                 if (looking_at(buf, &i, "* shouts: ") ||
2912                     looking_at(buf, &i, "--> ")) {
2913                     if (appData.colorize) {
2914                         if (oldi > next_out) {
2915                             SendToPlayer(&buf[next_out], oldi - next_out);
2916                             next_out = oldi;
2917                         }
2918                         Colorize(ColorShout, FALSE);
2919                         curColor = ColorShout;
2920                     }
2921                     loggedOn = TRUE;
2922                     started = STARTED_CHATTER;
2923                     continue;
2924                 }
2925
2926                 if (looking_at( buf, &i, "Challenge:")) {
2927                     if (appData.colorize) {
2928                         if (oldi > next_out) {
2929                             SendToPlayer(&buf[next_out], oldi - next_out);
2930                             next_out = oldi;
2931                         }
2932                         Colorize(ColorChallenge, FALSE);
2933                         curColor = ColorChallenge;
2934                     }
2935                     loggedOn = TRUE;
2936                     continue;
2937                 }
2938
2939                 if (looking_at(buf, &i, "* offers you") ||
2940                     looking_at(buf, &i, "* offers to be") ||
2941                     looking_at(buf, &i, "* would like to") ||
2942                     looking_at(buf, &i, "* requests to") ||
2943                     looking_at(buf, &i, "Your opponent offers") ||
2944                     looking_at(buf, &i, "Your opponent requests")) {
2945
2946                     if (appData.colorize) {
2947                         if (oldi > next_out) {
2948                             SendToPlayer(&buf[next_out], oldi - next_out);
2949                             next_out = oldi;
2950                         }
2951                         Colorize(ColorRequest, FALSE);
2952                         curColor = ColorRequest;
2953                     }
2954                     continue;
2955                 }
2956
2957                 if (looking_at(buf, &i, "* (*) seeking")) {
2958                     if (appData.colorize) {
2959                         if (oldi > next_out) {
2960                             SendToPlayer(&buf[next_out], oldi - next_out);
2961                             next_out = oldi;
2962                         }
2963                         Colorize(ColorSeek, FALSE);
2964                         curColor = ColorSeek;
2965                     }
2966                     continue;
2967             }
2968
2969             if (looking_at(buf, &i, "\\   ")) {
2970                 if (prevColor != ColorNormal) {
2971                     if (oldi > next_out) {
2972                         SendToPlayer(&buf[next_out], oldi - next_out);
2973                         next_out = oldi;
2974                     }
2975                     Colorize(prevColor, TRUE);
2976                     curColor = prevColor;
2977                 }
2978                 if (savingComment) {
2979                     parse_pos = i - oldi;
2980                     memcpy(parse, &buf[oldi], parse_pos);
2981                     parse[parse_pos] = NULLCHAR;
2982                     started = STARTED_COMMENT;
2983                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2984                         chattingPartner = savingComment - 3; // kludge to remember the box
2985                 } else {
2986                     started = STARTED_CHATTER;
2987                 }
2988                 continue;
2989             }
2990
2991             if (looking_at(buf, &i, "Black Strength :") ||
2992                 looking_at(buf, &i, "<<< style 10 board >>>") ||
2993                 looking_at(buf, &i, "<10>") ||
2994                 looking_at(buf, &i, "#@#")) {
2995                 /* Wrong board style */
2996                 loggedOn = TRUE;
2997                 SendToICS(ics_prefix);
2998                 SendToICS("set style 12\n");
2999                 SendToICS(ics_prefix);
3000                 SendToICS("refresh\n");
3001                 continue;
3002             }
3003             
3004             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3005                 ICSInitScript();
3006                 have_sent_ICS_logon = 1;
3007                 continue;
3008             }
3009               
3010             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3011                 (looking_at(buf, &i, "\n<12> ") ||
3012                  looking_at(buf, &i, "<12> "))) {
3013                 loggedOn = TRUE;
3014                 if (oldi > next_out) {
3015                     SendToPlayer(&buf[next_out], oldi - next_out);
3016                 }
3017                 next_out = i;
3018                 started = STARTED_BOARD;
3019                 parse_pos = 0;
3020                 continue;
3021             }
3022
3023             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3024                 looking_at(buf, &i, "<b1> ")) {
3025                 if (oldi > next_out) {
3026                     SendToPlayer(&buf[next_out], oldi - next_out);
3027                 }
3028                 next_out = i;
3029                 started = STARTED_HOLDINGS;
3030                 parse_pos = 0;
3031                 continue;
3032             }
3033
3034             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3035                 loggedOn = TRUE;
3036                 /* Header for a move list -- first line */
3037
3038                 switch (ics_getting_history) {
3039                   case H_FALSE:
3040                     switch (gameMode) {
3041                       case IcsIdle:
3042                       case BeginningOfGame:
3043                         /* User typed "moves" or "oldmoves" while we
3044                            were idle.  Pretend we asked for these
3045                            moves and soak them up so user can step
3046                            through them and/or save them.
3047                            */
3048                         Reset(FALSE, TRUE);
3049                         gameMode = IcsObserving;
3050                         ModeHighlight();
3051                         ics_gamenum = -1;
3052                         ics_getting_history = H_GOT_UNREQ_HEADER;
3053                         break;
3054                       case EditGame: /*?*/
3055                       case EditPosition: /*?*/
3056                         /* Should above feature work in these modes too? */
3057                         /* For now it doesn't */
3058                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3059                         break;
3060                       default:
3061                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3062                         break;
3063                     }
3064                     break;
3065                   case H_REQUESTED:
3066                     /* Is this the right one? */
3067                     if (gameInfo.white && gameInfo.black &&
3068                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3069                         strcmp(gameInfo.black, star_match[2]) == 0) {
3070                         /* All is well */
3071                         ics_getting_history = H_GOT_REQ_HEADER;
3072                     }
3073                     break;
3074                   case H_GOT_REQ_HEADER:
3075                   case H_GOT_UNREQ_HEADER:
3076                   case H_GOT_UNWANTED_HEADER:
3077                   case H_GETTING_MOVES:
3078                     /* Should not happen */
3079                     DisplayError(_("Error gathering move list: two headers"), 0);
3080                     ics_getting_history = H_FALSE;
3081                     break;
3082                 }
3083
3084                 /* Save player ratings into gameInfo if needed */
3085                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3086                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3087                     (gameInfo.whiteRating == -1 ||
3088                      gameInfo.blackRating == -1)) {
3089
3090                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3091                     gameInfo.blackRating = string_to_rating(star_match[3]);
3092                     if (appData.debugMode)
3093                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), 
3094                               gameInfo.whiteRating, gameInfo.blackRating);
3095                 }
3096                 continue;
3097             }
3098
3099             if (looking_at(buf, &i,
3100               "* * match, initial time: * minute*, increment: * second")) {
3101                 /* Header for a move list -- second line */
3102                 /* Initial board will follow if this is a wild game */
3103                 if (gameInfo.event != NULL) free(gameInfo.event);
3104                 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3105                 gameInfo.event = StrSave(str);
3106                 /* [HGM] we switched variant. Translate boards if needed. */
3107                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3108                 continue;
3109             }
3110
3111             if (looking_at(buf, &i, "Move  ")) {
3112                 /* Beginning of a move list */
3113                 switch (ics_getting_history) {
3114                   case H_FALSE:
3115                     /* Normally should not happen */
3116                     /* Maybe user hit reset while we were parsing */
3117                     break;
3118                   case H_REQUESTED:
3119                     /* Happens if we are ignoring a move list that is not
3120                      * the one we just requested.  Common if the user
3121                      * tries to observe two games without turning off
3122                      * getMoveList */
3123                     break;
3124                   case H_GETTING_MOVES:
3125                     /* Should not happen */
3126                     DisplayError(_("Error gathering move list: nested"), 0);
3127                     ics_getting_history = H_FALSE;
3128                     break;
3129                   case H_GOT_REQ_HEADER:
3130                     ics_getting_history = H_GETTING_MOVES;
3131                     started = STARTED_MOVES;
3132                     parse_pos = 0;
3133                     if (oldi > next_out) {
3134                         SendToPlayer(&buf[next_out], oldi - next_out);
3135                     }
3136                     break;
3137                   case H_GOT_UNREQ_HEADER:
3138                     ics_getting_history = H_GETTING_MOVES;
3139                     started = STARTED_MOVES_NOHIDE;
3140                     parse_pos = 0;
3141                     break;
3142                   case H_GOT_UNWANTED_HEADER:
3143                     ics_getting_history = H_FALSE;
3144                     break;
3145                 }
3146                 continue;
3147             }                           
3148             
3149             if (looking_at(buf, &i, "% ") ||
3150                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3151                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3152                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3153                     soughtPending = FALSE;
3154                     seekGraphUp = TRUE;
3155                     DrawSeekGraph();
3156                 }
3157                 if(suppressKibitz) next_out = i;
3158                 savingComment = FALSE;
3159                 suppressKibitz = 0;
3160                 switch (started) {
3161                   case STARTED_MOVES:
3162                   case STARTED_MOVES_NOHIDE:
3163                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3164                     parse[parse_pos + i - oldi] = NULLCHAR;
3165                     ParseGameHistory(parse);
3166 #if ZIPPY
3167                     if (appData.zippyPlay && first.initDone) {
3168                         FeedMovesToProgram(&first, forwardMostMove);
3169                         if (gameMode == IcsPlayingWhite) {
3170                             if (WhiteOnMove(forwardMostMove)) {
3171                                 if (first.sendTime) {
3172                                   if (first.useColors) {
3173                                     SendToProgram("black\n", &first); 
3174                                   }
3175                                   SendTimeRemaining(&first, TRUE);
3176                                 }
3177                                 if (first.useColors) {
3178                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3179                                 }
3180                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3181                                 first.maybeThinking = TRUE;
3182                             } else {
3183                                 if (first.usePlayother) {
3184                                   if (first.sendTime) {
3185                                     SendTimeRemaining(&first, TRUE);
3186                                   }
3187                                   SendToProgram("playother\n", &first);
3188                                   firstMove = FALSE;
3189                                 } else {
3190                                   firstMove = TRUE;
3191                                 }
3192                             }
3193                         } else if (gameMode == IcsPlayingBlack) {
3194                             if (!WhiteOnMove(forwardMostMove)) {
3195                                 if (first.sendTime) {
3196                                   if (first.useColors) {
3197                                     SendToProgram("white\n", &first);
3198                                   }
3199                                   SendTimeRemaining(&first, FALSE);
3200                                 }
3201                                 if (first.useColors) {
3202                                   SendToProgram("black\n", &first);
3203                                 }
3204                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3205                                 first.maybeThinking = TRUE;
3206                             } else {
3207                                 if (first.usePlayother) {
3208                                   if (first.sendTime) {
3209                                     SendTimeRemaining(&first, FALSE);
3210                                   }
3211                                   SendToProgram("playother\n", &first);
3212                                   firstMove = FALSE;
3213                                 } else {
3214                                   firstMove = TRUE;
3215                                 }
3216                             }
3217                         }                       
3218                     }
3219 #endif
3220                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3221                         /* Moves came from oldmoves or moves command
3222                            while we weren't doing anything else.
3223                            */
3224                         currentMove = forwardMostMove;
3225                         ClearHighlights();/*!!could figure this out*/
3226                         flipView = appData.flipView;
3227                         DrawPosition(TRUE, boards[currentMove]);
3228                         DisplayBothClocks();
3229                         sprintf(str, "%s vs. %s",
3230                                 gameInfo.white, gameInfo.black);
3231                         DisplayTitle(str);
3232                         gameMode = IcsIdle;
3233                     } else {
3234                         /* Moves were history of an active game */
3235                         if (gameInfo.resultDetails != NULL) {
3236                             free(gameInfo.resultDetails);
3237                             gameInfo.resultDetails = NULL;
3238                         }
3239                     }
3240                     HistorySet(parseList, backwardMostMove,
3241                                forwardMostMove, currentMove-1);
3242                     DisplayMove(currentMove - 1);
3243                     if (started == STARTED_MOVES) next_out = i;
3244                     started = STARTED_NONE;
3245                     ics_getting_history = H_FALSE;
3246                     break;
3247
3248                   case STARTED_OBSERVE:
3249                     started = STARTED_NONE;
3250                     SendToICS(ics_prefix);
3251                     SendToICS("refresh\n");
3252                     break;
3253
3254                   default:
3255                     break;
3256                 }
3257                 if(bookHit) { // [HGM] book: simulate book reply
3258                     static char bookMove[MSG_SIZ]; // a bit generous?
3259
3260                     programStats.nodes = programStats.depth = programStats.time = 
3261                     programStats.score = programStats.got_only_move = 0;
3262                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3263
3264                     strcpy(bookMove, "move ");
3265                     strcat(bookMove, bookHit);
3266                     HandleMachineMove(bookMove, &first);
3267                 }
3268                 continue;
3269             }
3270             
3271             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3272                  started == STARTED_HOLDINGS ||
3273                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3274                 /* Accumulate characters in move list or board */
3275                 parse[parse_pos++] = buf[i];
3276             }
3277             
3278             /* Start of game messages.  Mostly we detect start of game
3279                when the first board image arrives.  On some versions
3280                of the ICS, though, we need to do a "refresh" after starting
3281                to observe in order to get the current board right away. */
3282             if (looking_at(buf, &i, "Adding game * to observation list")) {
3283                 started = STARTED_OBSERVE;
3284                 continue;
3285             }
3286
3287             /* Handle auto-observe */
3288             if (appData.autoObserve &&
3289                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3290                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3291                 char *player;
3292                 /* Choose the player that was highlighted, if any. */
3293                 if (star_match[0][0] == '\033' ||
3294                     star_match[1][0] != '\033') {
3295                     player = star_match[0];
3296                 } else {
3297                     player = star_match[2];
3298                 }
3299                 sprintf(str, "%sobserve %s\n",
3300                         ics_prefix, StripHighlightAndTitle(player));
3301                 SendToICS(str);
3302
3303                 /* Save ratings from notify string */
3304                 strcpy(player1Name, star_match[0]);
3305                 player1Rating = string_to_rating(star_match[1]);
3306                 strcpy(player2Name, star_match[2]);
3307                 player2Rating = string_to_rating(star_match[3]);
3308
3309                 if (appData.debugMode)
3310                   fprintf(debugFP, 
3311                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3312                           player1Name, player1Rating,
3313                           player2Name, player2Rating);
3314
3315                 continue;
3316             }
3317
3318             /* Deal with automatic examine mode after a game,
3319                and with IcsObserving -> IcsExamining transition */
3320             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3321                 looking_at(buf, &i, "has made you an examiner of game *")) {
3322
3323                 int gamenum = atoi(star_match[0]);
3324                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3325                     gamenum == ics_gamenum) {
3326                     /* We were already playing or observing this game;
3327                        no need to refetch history */
3328                     gameMode = IcsExamining;
3329                     if (pausing) {
3330                         pauseExamForwardMostMove = forwardMostMove;
3331                     } else if (currentMove < forwardMostMove) {
3332                         ForwardInner(forwardMostMove);
3333                     }
3334                 } else {
3335                     /* I don't think this case really can happen */
3336                     SendToICS(ics_prefix);
3337                     SendToICS("refresh\n");
3338                 }
3339                 continue;
3340             }    
3341             
3342             /* Error messages */
3343 //          if (ics_user_moved) {
3344             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3345                 if (looking_at(buf, &i, "Illegal move") ||
3346                     looking_at(buf, &i, "Not a legal move") ||
3347                     looking_at(buf, &i, "Your king is in check") ||
3348                     looking_at(buf, &i, "It isn't your turn") ||
3349                     looking_at(buf, &i, "It is not your move")) {
3350                     /* Illegal move */
3351                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3352                         currentMove = forwardMostMove-1;
3353                         DisplayMove(currentMove - 1); /* before DMError */
3354                         DrawPosition(FALSE, boards[currentMove]);
3355                         SwitchClocks(forwardMostMove-1); // [HGM] race
3356                         DisplayBothClocks();
3357                     }
3358                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3359                     ics_user_moved = 0;
3360                     continue;
3361                 }
3362             }
3363
3364             if (looking_at(buf, &i, "still have time") ||
3365                 looking_at(buf, &i, "not out of time") ||
3366                 looking_at(buf, &i, "either player is out of time") ||
3367                 looking_at(buf, &i, "has timeseal; checking")) {
3368                 /* We must have called his flag a little too soon */
3369                 whiteFlag = blackFlag = FALSE;
3370                 continue;
3371             }
3372
3373             if (looking_at(buf, &i, "added * seconds to") ||
3374                 looking_at(buf, &i, "seconds were added to")) {
3375                 /* Update the clocks */
3376                 SendToICS(ics_prefix);
3377                 SendToICS("refresh\n");
3378                 continue;
3379             }
3380
3381             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3382                 ics_clock_paused = TRUE;
3383                 StopClocks();
3384                 continue;
3385             }
3386
3387             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3388                 ics_clock_paused = FALSE;
3389                 StartClocks();
3390                 continue;
3391             }
3392
3393             /* Grab player ratings from the Creating: message.
3394                Note we have to check for the special case when
3395                the ICS inserts things like [white] or [black]. */
3396             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3397                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3398                 /* star_matches:
3399                    0    player 1 name (not necessarily white)
3400                    1    player 1 rating
3401                    2    empty, white, or black (IGNORED)
3402                    3    player 2 name (not necessarily black)
3403                    4    player 2 rating
3404                    
3405                    The names/ratings are sorted out when the game
3406                    actually starts (below).
3407                 */
3408                 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3409                 player1Rating = string_to_rating(star_match[1]);
3410                 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3411                 player2Rating = string_to_rating(star_match[4]);
3412
3413                 if (appData.debugMode)
3414                   fprintf(debugFP, 
3415                           "Ratings from 'Creating:' %s %d, %s %d\n",
3416                           player1Name, player1Rating,
3417                           player2Name, player2Rating);
3418
3419                 continue;
3420             }
3421             
3422             /* Improved generic start/end-of-game messages */
3423             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3424                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3425                 /* If tkind == 0: */
3426                 /* star_match[0] is the game number */
3427                 /*           [1] is the white player's name */
3428                 /*           [2] is the black player's name */
3429                 /* For end-of-game: */
3430                 /*           [3] is the reason for the game end */
3431                 /*           [4] is a PGN end game-token, preceded by " " */
3432                 /* For start-of-game: */
3433                 /*           [3] begins with "Creating" or "Continuing" */
3434                 /*           [4] is " *" or empty (don't care). */
3435                 int gamenum = atoi(star_match[0]);
3436                 char *whitename, *blackname, *why, *endtoken;
3437                 ChessMove endtype = (ChessMove) 0;
3438
3439                 if (tkind == 0) {
3440                   whitename = star_match[1];
3441                   blackname = star_match[2];
3442                   why = star_match[3];
3443                   endtoken = star_match[4];
3444                 } else {
3445                   whitename = star_match[1];
3446                   blackname = star_match[3];
3447                   why = star_match[5];
3448                   endtoken = star_match[6];
3449                 }
3450
3451                 /* Game start messages */
3452                 if (strncmp(why, "Creating ", 9) == 0 ||
3453                     strncmp(why, "Continuing ", 11) == 0) {
3454                     gs_gamenum = gamenum;
3455                     strcpy(gs_kind, strchr(why, ' ') + 1);
3456                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3457 #if ZIPPY
3458                     if (appData.zippyPlay) {
3459                         ZippyGameStart(whitename, blackname);
3460                     }
3461 #endif /*ZIPPY*/
3462                     continue;
3463                 }
3464
3465                 /* Game end messages */
3466                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3467                     ics_gamenum != gamenum) {
3468                     continue;
3469                 }
3470                 while (endtoken[0] == ' ') endtoken++;
3471                 switch (endtoken[0]) {
3472                   case '*':
3473                   default:
3474                     endtype = GameUnfinished;
3475                     break;
3476                   case '0':
3477                     endtype = BlackWins;
3478                     break;
3479                   case '1':
3480                     if (endtoken[1] == '/')
3481                       endtype = GameIsDrawn;
3482                     else
3483                       endtype = WhiteWins;
3484                     break;
3485                 }
3486                 GameEnds(endtype, why, GE_ICS);
3487 #if ZIPPY
3488                 if (appData.zippyPlay && first.initDone) {
3489                     ZippyGameEnd(endtype, why);
3490                     if (first.pr == NULL) {
3491                       /* Start the next process early so that we'll
3492                          be ready for the next challenge */
3493                       StartChessProgram(&first);
3494                     }
3495                     /* Send "new" early, in case this command takes
3496                        a long time to finish, so that we'll be ready
3497                        for the next challenge. */
3498                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3499                     Reset(TRUE, TRUE);
3500                 }
3501 #endif /*ZIPPY*/
3502                 continue;
3503             }
3504
3505             if (looking_at(buf, &i, "Removing game * from observation") ||
3506                 looking_at(buf, &i, "no longer observing game *") ||
3507                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3508                 if (gameMode == IcsObserving &&
3509                     atoi(star_match[0]) == ics_gamenum)
3510                   {
3511                       /* icsEngineAnalyze */
3512                       if (appData.icsEngineAnalyze) {
3513                             ExitAnalyzeMode();
3514                             ModeHighlight();
3515                       }
3516                       StopClocks();
3517                       gameMode = IcsIdle;
3518                       ics_gamenum = -1;
3519                       ics_user_moved = FALSE;
3520                   }
3521                 continue;
3522             }
3523
3524             if (looking_at(buf, &i, "no longer examining game *")) {
3525                 if (gameMode == IcsExamining &&
3526                     atoi(star_match[0]) == ics_gamenum)
3527                   {
3528                       gameMode = IcsIdle;
3529                       ics_gamenum = -1;
3530                       ics_user_moved = FALSE;
3531                   }
3532                 continue;
3533             }
3534
3535             /* Advance leftover_start past any newlines we find,
3536                so only partial lines can get reparsed */
3537             if (looking_at(buf, &i, "\n")) {
3538                 prevColor = curColor;
3539                 if (curColor != ColorNormal) {
3540                     if (oldi > next_out) {
3541                         SendToPlayer(&buf[next_out], oldi - next_out);
3542                         next_out = oldi;
3543                     }
3544                     Colorize(ColorNormal, FALSE);
3545                     curColor = ColorNormal;
3546                 }
3547                 if (started == STARTED_BOARD) {
3548                     started = STARTED_NONE;
3549                     parse[parse_pos] = NULLCHAR;
3550                     ParseBoard12(parse);
3551                     ics_user_moved = 0;
3552
3553                     /* Send premove here */
3554                     if (appData.premove) {
3555                       char str[MSG_SIZ];
3556                       if (currentMove == 0 &&
3557                           gameMode == IcsPlayingWhite &&
3558                           appData.premoveWhite) {
3559                         sprintf(str, "%s\n", appData.premoveWhiteText);
3560                         if (appData.debugMode)
3561                           fprintf(debugFP, "Sending premove:\n");
3562                         SendToICS(str);
3563                       } else if (currentMove == 1 &&
3564                                  gameMode == IcsPlayingBlack &&
3565                                  appData.premoveBlack) {
3566                         sprintf(str, "%s\n", appData.premoveBlackText);
3567                         if (appData.debugMode)
3568                           fprintf(debugFP, "Sending premove:\n");
3569                         SendToICS(str);
3570                       } else if (gotPremove) {
3571                         gotPremove = 0;
3572                         ClearPremoveHighlights();
3573                         if (appData.debugMode)
3574                           fprintf(debugFP, "Sending premove:\n");
3575                           UserMoveEvent(premoveFromX, premoveFromY, 
3576                                         premoveToX, premoveToY, 
3577                                         premovePromoChar);
3578                       }
3579                     }
3580
3581                     /* Usually suppress following prompt */
3582                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3583                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3584                         if (looking_at(buf, &i, "*% ")) {
3585                             savingComment = FALSE;
3586                             suppressKibitz = 0;
3587                         }
3588                     }
3589                     next_out = i;
3590                 } else if (started == STARTED_HOLDINGS) {
3591                     int gamenum;
3592                     char new_piece[MSG_SIZ];
3593                     started = STARTED_NONE;
3594                     parse[parse_pos] = NULLCHAR;
3595                     if (appData.debugMode)
3596                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3597                                                         parse, currentMove);
3598                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3599                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3600                         if (gameInfo.variant == VariantNormal) {
3601                           /* [HGM] We seem to switch variant during a game!
3602                            * Presumably no holdings were displayed, so we have
3603                            * to move the position two files to the right to
3604                            * create room for them!
3605                            */
3606                           VariantClass newVariant;
3607                           switch(gameInfo.boardWidth) { // base guess on board width
3608                                 case 9:  newVariant = VariantShogi; break;
3609                                 case 10: newVariant = VariantGreat; break;
3610                                 default: newVariant = VariantCrazyhouse; break;
3611                           }
3612                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3613                           /* Get a move list just to see the header, which
3614                              will tell us whether this is really bug or zh */
3615                           if (ics_getting_history == H_FALSE) {
3616                             ics_getting_history = H_REQUESTED;
3617                             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3618                             SendToICS(str);
3619                           }
3620                         }
3621                         new_piece[0] = NULLCHAR;
3622                         sscanf(parse, "game %d white [%s black [%s <- %s",
3623                                &gamenum, white_holding, black_holding,
3624                                new_piece);
3625                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3626                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3627                         /* [HGM] copy holdings to board holdings area */
3628                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3629                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3630                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3631 #if ZIPPY
3632                         if (appData.zippyPlay && first.initDone) {
3633                             ZippyHoldings(white_holding, black_holding,
3634                                           new_piece);
3635                         }
3636 #endif /*ZIPPY*/
3637                         if (tinyLayout || smallLayout) {
3638                             char wh[16], bh[16];
3639                             PackHolding(wh, white_holding);
3640                             PackHolding(bh, black_holding);
3641                             sprintf(str, "[%s-%s] %s-%s", wh, bh,
3642                                     gameInfo.white, gameInfo.black);
3643                         } else {
3644                             sprintf(str, "%s [%s] vs. %s [%s]",
3645                                     gameInfo.white, white_holding,
3646                                     gameInfo.black, black_holding);
3647                         }
3648
3649                         DrawPosition(FALSE, boards[currentMove]);
3650                         DisplayTitle(str);
3651                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3652                         sscanf(parse, "game %d white [%s black [%s <- %s",
3653                                &gamenum, white_holding, black_holding,
3654                                new_piece);
3655                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3656                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3657                         /* [HGM] copy holdings to partner-board holdings area */
3658                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3659                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3660                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3661                       }
3662                     }
3663                     /* Suppress following prompt */
3664                     if (looking_at(buf, &i, "*% ")) {
3665                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3666                         savingComment = FALSE;
3667                         suppressKibitz = 0;
3668                     }
3669                     next_out = i;
3670                 }
3671                 continue;
3672             }
3673
3674             i++;                /* skip unparsed character and loop back */
3675         }
3676         
3677         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3678 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3679 //          SendToPlayer(&buf[next_out], i - next_out);
3680             started != STARTED_HOLDINGS && leftover_start > next_out) {
3681             SendToPlayer(&buf[next_out], leftover_start - next_out);
3682             next_out = i;
3683         }
3684         
3685         leftover_len = buf_len - leftover_start;
3686         /* if buffer ends with something we couldn't parse,
3687            reparse it after appending the next read */
3688         
3689     } else if (count == 0) {
3690         RemoveInputSource(isr);
3691         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3692     } else {
3693         DisplayFatalError(_("Error reading from ICS"), error, 1);
3694     }
3695 }
3696
3697
3698 /* Board style 12 looks like this:
3699    
3700    <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
3701    
3702  * The "<12> " is stripped before it gets to this routine.  The two
3703  * trailing 0's (flip state and clock ticking) are later addition, and
3704  * some chess servers may not have them, or may have only the first.
3705  * Additional trailing fields may be added in the future.  
3706  */
3707
3708 #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"
3709
3710 #define RELATION_OBSERVING_PLAYED    0
3711 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3712 #define RELATION_PLAYING_MYMOVE      1
3713 #define RELATION_PLAYING_NOTMYMOVE  -1
3714 #define RELATION_EXAMINING           2
3715 #define RELATION_ISOLATED_BOARD     -3
3716 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3717
3718 void
3719 ParseBoard12(string)
3720      char *string;
3721
3722     GameMode newGameMode;
3723     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3724     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3725     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3726     char to_play, board_chars[200];
3727     char move_str[500], str[500], elapsed_time[500];
3728     char black[32], white[32];
3729     Board board;
3730     int prevMove = currentMove;
3731     int ticking = 2;
3732     ChessMove moveType;
3733     int fromX, fromY, toX, toY;
3734     char promoChar;
3735     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3736     char *bookHit = NULL; // [HGM] book
3737     Boolean weird = FALSE, reqFlag = FALSE;
3738
3739     fromX = fromY = toX = toY = -1;
3740     
3741     newGame = FALSE;
3742
3743     if (appData.debugMode)
3744       fprintf(debugFP, _("Parsing board: %s\n"), string);
3745
3746     move_str[0] = NULLCHAR;
3747     elapsed_time[0] = NULLCHAR;
3748     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3749         int  i = 0, j;
3750         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3751             if(string[i] == ' ') { ranks++; files = 0; }
3752             else files++;
3753             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3754             i++;
3755         }
3756         for(j = 0; j <i; j++) board_chars[j] = string[j];
3757         board_chars[i] = '\0';
3758         string += i + 1;
3759     }
3760     n = sscanf(string, PATTERN, &to_play, &double_push,
3761                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3762                &gamenum, white, black, &relation, &basetime, &increment,
3763                &white_stren, &black_stren, &white_time, &black_time,
3764                &moveNum, str, elapsed_time, move_str, &ics_flip,
3765                &ticking);
3766
3767     if (n < 21) {
3768         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3769         DisplayError(str, 0);
3770         return;
3771     }
3772
3773     /* Convert the move number to internal form */
3774     moveNum = (moveNum - 1) * 2;
3775     if (to_play == 'B') moveNum++;
3776     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3777       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3778                         0, 1);
3779       return;
3780     }
3781     
3782     switch (relation) {
3783       case RELATION_OBSERVING_PLAYED:
3784       case RELATION_OBSERVING_STATIC:
3785         if (gamenum == -1) {
3786             /* Old ICC buglet */
3787             relation = RELATION_OBSERVING_STATIC;
3788         }
3789         newGameMode = IcsObserving;
3790         break;
3791       case RELATION_PLAYING_MYMOVE:
3792       case RELATION_PLAYING_NOTMYMOVE:
3793         newGameMode =
3794           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3795             IcsPlayingWhite : IcsPlayingBlack;
3796         break;
3797       case RELATION_EXAMINING:
3798         newGameMode = IcsExamining;
3799         break;
3800       case RELATION_ISOLATED_BOARD:
3801       default:
3802         /* Just display this board.  If user was doing something else,
3803            we will forget about it until the next board comes. */ 
3804         newGameMode = IcsIdle;
3805         break;
3806       case RELATION_STARTING_POSITION:
3807         newGameMode = gameMode;
3808         break;
3809     }
3810     
3811     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3812          && newGameMode == IcsObserving && appData.bgObserve) {
3813       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3814       char buf[MSG_SIZ];
3815       for (k = 0; k < ranks; k++) {
3816         for (j = 0; j < files; j++)
3817           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3818         if(gameInfo.holdingsWidth > 1) {
3819              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3820              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3821         }
3822       }
3823       CopyBoard(partnerBoard, board);
3824       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3825       sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3826                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3827       DisplayMessage(buf, "");
3828       return;
3829     }
3830
3831     /* Modify behavior for initial board display on move listing
3832        of wild games.
3833        */
3834     switch (ics_getting_history) {
3835       case H_FALSE:
3836       case H_REQUESTED:
3837         break;
3838       case H_GOT_REQ_HEADER:
3839       case H_GOT_UNREQ_HEADER:
3840         /* This is the initial position of the current game */
3841         gamenum = ics_gamenum;
3842         moveNum = 0;            /* old ICS bug workaround */
3843         if (to_play == 'B') {
3844           startedFromSetupPosition = TRUE;
3845           blackPlaysFirst = TRUE;
3846           moveNum = 1;
3847           if (forwardMostMove == 0) forwardMostMove = 1;
3848           if (backwardMostMove == 0) backwardMostMove = 1;
3849           if (currentMove == 0) currentMove = 1;
3850         }
3851         newGameMode = gameMode;
3852         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3853         break;
3854       case H_GOT_UNWANTED_HEADER:
3855         /* This is an initial board that we don't want */
3856         return;
3857       case H_GETTING_MOVES:
3858         /* Should not happen */
3859         DisplayError(_("Error gathering move list: extra board"), 0);
3860         ics_getting_history = H_FALSE;
3861         return;
3862     }
3863
3864    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || 
3865                                         weird && (int)gameInfo.variant <= (int)VariantShogi) {
3866      /* [HGM] We seem to have switched variant unexpectedly
3867       * Try to guess new variant from board size
3868       */
3869           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3870           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3871           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3872           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3873           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
3874           if(!weird) newVariant = VariantNormal;
3875           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3876           /* Get a move list just to see the header, which
3877              will tell us whether this is really bug or zh */
3878           if (ics_getting_history == H_FALSE) {
3879             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3880             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3881             SendToICS(str);
3882           }
3883     }
3884     
3885     /* Take action if this is the first board of a new game, or of a
3886        different game than is currently being displayed.  */
3887     if (gamenum != ics_gamenum || newGameMode != gameMode ||
3888         relation == RELATION_ISOLATED_BOARD) {
3889         
3890         /* Forget the old game and get the history (if any) of the new one */
3891         if (gameMode != BeginningOfGame) {
3892           Reset(TRUE, TRUE);
3893         }
3894         newGame = TRUE;
3895         if (appData.autoRaiseBoard) BoardToTop();
3896         prevMove = -3;
3897         if (gamenum == -1) {
3898             newGameMode = IcsIdle;
3899         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3900                    appData.getMoveList && !reqFlag) {
3901             /* Need to get game history */
3902             ics_getting_history = H_REQUESTED;
3903             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3904             SendToICS(str);
3905         }
3906         
3907         /* Initially flip the board to have black on the bottom if playing
3908            black or if the ICS flip flag is set, but let the user change
3909            it with the Flip View button. */
3910         flipView = appData.autoFlipView ? 
3911           (newGameMode == IcsPlayingBlack) || ics_flip :
3912           appData.flipView;
3913         
3914         /* Done with values from previous mode; copy in new ones */
3915         gameMode = newGameMode;
3916         ModeHighlight();
3917         ics_gamenum = gamenum;
3918         if (gamenum == gs_gamenum) {
3919             int klen = strlen(gs_kind);
3920             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3921             sprintf(str, "ICS %s", gs_kind);
3922             gameInfo.event = StrSave(str);
3923         } else {
3924             gameInfo.event = StrSave("ICS game");
3925         }
3926         gameInfo.site = StrSave(appData.icsHost);
3927         gameInfo.date = PGNDate();
3928         gameInfo.round = StrSave("-");
3929         gameInfo.white = StrSave(white);
3930         gameInfo.black = StrSave(black);
3931         timeControl = basetime * 60 * 1000;
3932         timeControl_2 = 0;
3933         timeIncrement = increment * 1000;
3934         movesPerSession = 0;
3935         gameInfo.timeControl = TimeControlTagValue();
3936         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3937   if (appData.debugMode) {
3938     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3939     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3940     setbuf(debugFP, NULL);
3941   }
3942
3943         gameInfo.outOfBook = NULL;
3944         
3945         /* Do we have the ratings? */
3946         if (strcmp(player1Name, white) == 0 &&
3947             strcmp(player2Name, black) == 0) {
3948             if (appData.debugMode)
3949               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3950                       player1Rating, player2Rating);
3951             gameInfo.whiteRating = player1Rating;
3952             gameInfo.blackRating = player2Rating;
3953         } else if (strcmp(player2Name, white) == 0 &&
3954                    strcmp(player1Name, black) == 0) {
3955             if (appData.debugMode)
3956               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3957                       player2Rating, player1Rating);
3958             gameInfo.whiteRating = player2Rating;
3959             gameInfo.blackRating = player1Rating;
3960         }
3961         player1Name[0] = player2Name[0] = NULLCHAR;
3962
3963         /* Silence shouts if requested */
3964         if (appData.quietPlay &&
3965             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3966             SendToICS(ics_prefix);
3967             SendToICS("set shout 0\n");
3968         }
3969     }
3970     
3971     /* Deal with midgame name changes */
3972     if (!newGame) {
3973         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3974             if (gameInfo.white) free(gameInfo.white);
3975             gameInfo.white = StrSave(white);
3976         }
3977         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3978             if (gameInfo.black) free(gameInfo.black);
3979             gameInfo.black = StrSave(black);
3980         }
3981     }
3982     
3983     /* Throw away game result if anything actually changes in examine mode */
3984     if (gameMode == IcsExamining && !newGame) {
3985         gameInfo.result = GameUnfinished;
3986         if (gameInfo.resultDetails != NULL) {
3987             free(gameInfo.resultDetails);
3988             gameInfo.resultDetails = NULL;
3989         }
3990     }
3991     
3992     /* In pausing && IcsExamining mode, we ignore boards coming
3993        in if they are in a different variation than we are. */
3994     if (pauseExamInvalid) return;
3995     if (pausing && gameMode == IcsExamining) {
3996         if (moveNum <= pauseExamForwardMostMove) {
3997             pauseExamInvalid = TRUE;
3998             forwardMostMove = pauseExamForwardMostMove;
3999             return;
4000         }
4001     }
4002     
4003   if (appData.debugMode) {
4004     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4005   }
4006     /* Parse the board */
4007     for (k = 0; k < ranks; k++) {
4008       for (j = 0; j < files; j++)
4009         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4010       if(gameInfo.holdingsWidth > 1) {
4011            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4012            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4013       }
4014     }
4015     CopyBoard(boards[moveNum], board);
4016     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4017     if (moveNum == 0) {
4018         startedFromSetupPosition =
4019           !CompareBoards(board, initialPosition);
4020         if(startedFromSetupPosition)
4021             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4022     }
4023
4024     /* [HGM] Set castling rights. Take the outermost Rooks,
4025        to make it also work for FRC opening positions. Note that board12
4026        is really defective for later FRC positions, as it has no way to
4027        indicate which Rook can castle if they are on the same side of King.
4028        For the initial position we grant rights to the outermost Rooks,
4029        and remember thos rights, and we then copy them on positions
4030        later in an FRC game. This means WB might not recognize castlings with
4031        Rooks that have moved back to their original position as illegal,
4032        but in ICS mode that is not its job anyway.
4033     */
4034     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4035     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4036
4037         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4038             if(board[0][i] == WhiteRook) j = i;
4039         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4040         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4041             if(board[0][i] == WhiteRook) j = i;
4042         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4043         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4044             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4045         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4046         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4047             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4048         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4049
4050         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4051         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4052             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4053         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4054             if(board[BOARD_HEIGHT-1][k] == bKing)
4055                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4056         if(gameInfo.variant == VariantTwoKings) {
4057             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4058             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4059             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4060         }
4061     } else { int r;
4062         r = boards[moveNum][CASTLING][0] = initialRights[0];
4063         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4064         r = boards[moveNum][CASTLING][1] = initialRights[1];
4065         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4066         r = boards[moveNum][CASTLING][3] = initialRights[3];
4067         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4068         r = boards[moveNum][CASTLING][4] = initialRights[4];
4069         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4070         /* wildcastle kludge: always assume King has rights */
4071         r = boards[moveNum][CASTLING][2] = initialRights[2];
4072         r = boards[moveNum][CASTLING][5] = initialRights[5];
4073     }
4074     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4075     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4076
4077     
4078     if (ics_getting_history == H_GOT_REQ_HEADER ||
4079         ics_getting_history == H_GOT_UNREQ_HEADER) {
4080         /* This was an initial position from a move list, not
4081            the current position */
4082         return;
4083     }
4084     
4085     /* Update currentMove and known move number limits */
4086     newMove = newGame || moveNum > forwardMostMove;
4087
4088     if (newGame) {
4089         forwardMostMove = backwardMostMove = currentMove = moveNum;
4090         if (gameMode == IcsExamining && moveNum == 0) {
4091           /* Workaround for ICS limitation: we are not told the wild
4092              type when starting to examine a game.  But if we ask for
4093              the move list, the move list header will tell us */
4094             ics_getting_history = H_REQUESTED;
4095             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4096             SendToICS(str);
4097         }
4098     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4099                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4100 #if ZIPPY
4101         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4102         /* [HGM] applied this also to an engine that is silently watching        */
4103         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4104             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4105             gameInfo.variant == currentlyInitializedVariant) {
4106           takeback = forwardMostMove - moveNum;
4107           for (i = 0; i < takeback; i++) {
4108             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4109             SendToProgram("undo\n", &first);
4110           }
4111         }
4112 #endif
4113
4114         forwardMostMove = moveNum;
4115         if (!pausing || currentMove > forwardMostMove)
4116           currentMove = forwardMostMove;
4117     } else {
4118         /* New part of history that is not contiguous with old part */ 
4119         if (pausing && gameMode == IcsExamining) {
4120             pauseExamInvalid = TRUE;
4121             forwardMostMove = pauseExamForwardMostMove;
4122             return;
4123         }
4124         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4125 #if ZIPPY
4126             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4127                 // [HGM] when we will receive the move list we now request, it will be
4128                 // fed to the engine from the first move on. So if the engine is not
4129                 // in the initial position now, bring it there.
4130                 InitChessProgram(&first, 0);
4131             }
4132 #endif
4133             ics_getting_history = H_REQUESTED;
4134             sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4135             SendToICS(str);
4136         }
4137         forwardMostMove = backwardMostMove = currentMove = moveNum;
4138     }
4139     
4140     /* Update the clocks */
4141     if (strchr(elapsed_time, '.')) {
4142       /* Time is in ms */
4143       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4144       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4145     } else {
4146       /* Time is in seconds */
4147       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4148       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4149     }
4150       
4151
4152 #if ZIPPY
4153     if (appData.zippyPlay && newGame &&
4154         gameMode != IcsObserving && gameMode != IcsIdle &&
4155         gameMode != IcsExamining)
4156       ZippyFirstBoard(moveNum, basetime, increment);
4157 #endif
4158     
4159     /* Put the move on the move list, first converting
4160        to canonical algebraic form. */
4161     if (moveNum > 0) {
4162   if (appData.debugMode) {
4163     if (appData.debugMode) { int f = forwardMostMove;
4164         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4165                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4166                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4167     }
4168     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4169     fprintf(debugFP, "moveNum = %d\n", moveNum);
4170     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4171     setbuf(debugFP, NULL);
4172   }
4173         if (moveNum <= backwardMostMove) {
4174             /* We don't know what the board looked like before
4175                this move.  Punt. */
4176             strcpy(parseList[moveNum - 1], move_str);
4177             strcat(parseList[moveNum - 1], " ");
4178             strcat(parseList[moveNum - 1], elapsed_time);
4179             moveList[moveNum - 1][0] = NULLCHAR;
4180         } else if (strcmp(move_str, "none") == 0) {
4181             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4182             /* Again, we don't know what the board looked like;
4183                this is really the start of the game. */
4184             parseList[moveNum - 1][0] = NULLCHAR;
4185             moveList[moveNum - 1][0] = NULLCHAR;
4186             backwardMostMove = moveNum;
4187             startedFromSetupPosition = TRUE;
4188             fromX = fromY = toX = toY = -1;
4189         } else {
4190           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. 
4191           //                 So we parse the long-algebraic move string in stead of the SAN move
4192           int valid; char buf[MSG_SIZ], *prom;
4193
4194           // str looks something like "Q/a1-a2"; kill the slash
4195           if(str[1] == '/') 
4196                 sprintf(buf, "%c%s", str[0], str+2);
4197           else  strcpy(buf, str); // might be castling
4198           if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) 
4199                 strcat(buf, prom); // long move lacks promo specification!
4200           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4201                 if(appData.debugMode) 
4202                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4203                 strcpy(move_str, buf);
4204           }
4205           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4206                                 &fromX, &fromY, &toX, &toY, &promoChar)
4207                || ParseOneMove(buf, moveNum - 1, &moveType,
4208                                 &fromX, &fromY, &toX, &toY, &promoChar);
4209           // end of long SAN patch
4210           if (valid) {
4211             (void) CoordsToAlgebraic(boards[moveNum - 1],
4212                                      PosFlags(moveNum - 1),
4213                                      fromY, fromX, toY, toX, promoChar,
4214                                      parseList[moveNum-1]);
4215             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4216               case MT_NONE:
4217               case MT_STALEMATE:
4218               default:
4219                 break;
4220               case MT_CHECK:
4221                 if(gameInfo.variant != VariantShogi)
4222                     strcat(parseList[moveNum - 1], "+");
4223                 break;
4224               case MT_CHECKMATE:
4225               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4226                 strcat(parseList[moveNum - 1], "#");
4227                 break;
4228             }
4229             strcat(parseList[moveNum - 1], " ");
4230             strcat(parseList[moveNum - 1], elapsed_time);
4231             /* currentMoveString is set as a side-effect of ParseOneMove */
4232             strcpy(moveList[moveNum - 1], currentMoveString);
4233             strcat(moveList[moveNum - 1], "\n");
4234           } else {
4235             /* Move from ICS was illegal!?  Punt. */
4236   if (appData.debugMode) {
4237     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4238     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4239   }
4240             strcpy(parseList[moveNum - 1], move_str);
4241             strcat(parseList[moveNum - 1], " ");
4242             strcat(parseList[moveNum - 1], elapsed_time);
4243             moveList[moveNum - 1][0] = NULLCHAR;
4244             fromX = fromY = toX = toY = -1;
4245           }
4246         }
4247   if (appData.debugMode) {
4248     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4249     setbuf(debugFP, NULL);
4250   }
4251
4252 #if ZIPPY
4253         /* Send move to chess program (BEFORE animating it). */
4254         if (appData.zippyPlay && !newGame && newMove && 
4255            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4256
4257             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4258                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4259                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4260                     sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4261                             move_str);
4262                     DisplayError(str, 0);
4263                 } else {
4264                     if (first.sendTime) {
4265                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4266                     }
4267                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4268                     if (firstMove && !bookHit) {
4269                         firstMove = FALSE;
4270                         if (first.useColors) {
4271                           SendToProgram(gameMode == IcsPlayingWhite ?
4272                                         "white\ngo\n" :
4273                                         "black\ngo\n", &first);
4274                         } else {
4275                           SendToProgram("go\n", &first);
4276                         }
4277                         first.maybeThinking = TRUE;
4278                     }
4279                 }
4280             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4281               if (moveList[moveNum - 1][0] == NULLCHAR) {
4282                 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4283                 DisplayError(str, 0);
4284               } else {
4285                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4286                 SendMoveToProgram(moveNum - 1, &first);
4287               }
4288             }
4289         }
4290 #endif
4291     }
4292
4293     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4294         /* If move comes from a remote source, animate it.  If it
4295            isn't remote, it will have already been animated. */
4296         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4297             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4298         }
4299         if (!pausing && appData.highlightLastMove) {
4300             SetHighlights(fromX, fromY, toX, toY);
4301         }
4302     }
4303     
4304     /* Start the clocks */
4305     whiteFlag = blackFlag = FALSE;
4306     appData.clockMode = !(basetime == 0 && increment == 0);
4307     if (ticking == 0) {
4308       ics_clock_paused = TRUE;
4309       StopClocks();
4310     } else if (ticking == 1) {
4311       ics_clock_paused = FALSE;
4312     }
4313     if (gameMode == IcsIdle ||
4314         relation == RELATION_OBSERVING_STATIC ||
4315         relation == RELATION_EXAMINING ||
4316         ics_clock_paused)
4317       DisplayBothClocks();
4318     else
4319       StartClocks();
4320     
4321     /* Display opponents and material strengths */
4322     if (gameInfo.variant != VariantBughouse &&
4323         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4324         if (tinyLayout || smallLayout) {
4325             if(gameInfo.variant == VariantNormal)
4326                 sprintf(str, "%s(%d) %s(%d) {%d %d}", 
4327                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4328                     basetime, increment);
4329             else
4330                 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", 
4331                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4332                     basetime, increment, (int) gameInfo.variant);
4333         } else {
4334             if(gameInfo.variant == VariantNormal)
4335                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", 
4336                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4337                     basetime, increment);
4338             else
4339                 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", 
4340                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4341                     basetime, increment, VariantName(gameInfo.variant));
4342         }
4343         DisplayTitle(str);
4344   if (appData.debugMode) {
4345     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4346   }
4347     }
4348
4349
4350     /* Display the board */
4351     if (!pausing && !appData.noGUI) {
4352       
4353       if (appData.premove)
4354           if (!gotPremove || 
4355              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4356              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4357               ClearPremoveHighlights();
4358
4359       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4360       DrawPosition(j, boards[currentMove]);
4361
4362       DisplayMove(moveNum - 1);
4363       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4364             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4365               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4366         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4367       }
4368     }
4369
4370     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4371 #if ZIPPY
4372     if(bookHit) { // [HGM] book: simulate book reply
4373         static char bookMove[MSG_SIZ]; // a bit generous?
4374
4375         programStats.nodes = programStats.depth = programStats.time = 
4376         programStats.score = programStats.got_only_move = 0;
4377         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4378
4379         strcpy(bookMove, "move ");
4380         strcat(bookMove, bookHit);
4381         HandleMachineMove(bookMove, &first);
4382     }
4383 #endif
4384 }
4385
4386 void
4387 GetMoveListEvent()
4388 {
4389     char buf[MSG_SIZ];
4390     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4391         ics_getting_history = H_REQUESTED;
4392         sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4393         SendToICS(buf);
4394     }
4395 }
4396
4397 void
4398 AnalysisPeriodicEvent(force)
4399      int force;
4400 {
4401     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4402          && !force) || !appData.periodicUpdates)
4403       return;
4404
4405     /* Send . command to Crafty to collect stats */
4406     SendToProgram(".\n", &first);
4407
4408     /* Don't send another until we get a response (this makes
4409        us stop sending to old Crafty's which don't understand
4410        the "." command (sending illegal cmds resets node count & time,
4411        which looks bad)) */
4412     programStats.ok_to_send = 0;
4413 }
4414
4415 void ics_update_width(new_width)
4416         int new_width;
4417 {
4418         ics_printf("set width %d\n", new_width);
4419 }
4420
4421 void
4422 SendMoveToProgram(moveNum, cps)
4423      int moveNum;
4424      ChessProgramState *cps;
4425 {
4426     char buf[MSG_SIZ];
4427
4428     if (cps->useUsermove) {
4429       SendToProgram("usermove ", cps);
4430     }
4431     if (cps->useSAN) {
4432       char *space;
4433       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4434         int len = space - parseList[moveNum];
4435         memcpy(buf, parseList[moveNum], len);
4436         buf[len++] = '\n';
4437         buf[len] = NULLCHAR;
4438       } else {
4439         sprintf(buf, "%s\n", parseList[moveNum]);
4440       }
4441       SendToProgram(buf, cps);
4442     } else {
4443       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4444         AlphaRank(moveList[moveNum], 4);
4445         SendToProgram(moveList[moveNum], cps);
4446         AlphaRank(moveList[moveNum], 4); // and back
4447       } else
4448       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4449        * the engine. It would be nice to have a better way to identify castle 
4450        * moves here. */
4451       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4452                                                                          && cps->useOOCastle) {
4453         int fromX = moveList[moveNum][0] - AAA; 
4454         int fromY = moveList[moveNum][1] - ONE;
4455         int toX = moveList[moveNum][2] - AAA; 
4456         int toY = moveList[moveNum][3] - ONE;
4457         if((boards[moveNum][fromY][fromX] == WhiteKing 
4458             && boards[moveNum][toY][toX] == WhiteRook)
4459            || (boards[moveNum][fromY][fromX] == BlackKing 
4460                && boards[moveNum][toY][toX] == BlackRook)) {
4461           if(toX > fromX) SendToProgram("O-O\n", cps);
4462           else SendToProgram("O-O-O\n", cps);
4463         }
4464         else SendToProgram(moveList[moveNum], cps);
4465       }
4466       else SendToProgram(moveList[moveNum], cps);
4467       /* End of additions by Tord */
4468     }
4469
4470     /* [HGM] setting up the opening has brought engine in force mode! */
4471     /*       Send 'go' if we are in a mode where machine should play. */
4472     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4473         (gameMode == TwoMachinesPlay   ||
4474 #ifdef ZIPPY
4475          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4476 #endif
4477          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4478         SendToProgram("go\n", cps);
4479   if (appData.debugMode) {
4480     fprintf(debugFP, "(extra)\n");
4481   }
4482     }
4483     setboardSpoiledMachineBlack = 0;
4484 }
4485
4486 void
4487 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4488      ChessMove moveType;
4489      int fromX, fromY, toX, toY;
4490 {
4491     char user_move[MSG_SIZ];
4492
4493     switch (moveType) {
4494       default:
4495         sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4496                 (int)moveType, fromX, fromY, toX, toY);
4497         DisplayError(user_move + strlen("say "), 0);
4498         break;
4499       case WhiteKingSideCastle:
4500       case BlackKingSideCastle:
4501       case WhiteQueenSideCastleWild:
4502       case BlackQueenSideCastleWild:
4503       /* PUSH Fabien */
4504       case WhiteHSideCastleFR:
4505       case BlackHSideCastleFR:
4506       /* POP Fabien */
4507         sprintf(user_move, "o-o\n");
4508         break;
4509       case WhiteQueenSideCastle:
4510       case BlackQueenSideCastle:
4511       case WhiteKingSideCastleWild:
4512       case BlackKingSideCastleWild:
4513       /* PUSH Fabien */
4514       case WhiteASideCastleFR:
4515       case BlackASideCastleFR:
4516       /* POP Fabien */
4517         sprintf(user_move, "o-o-o\n");
4518         break;
4519       case WhitePromotionQueen:
4520       case BlackPromotionQueen:
4521       case WhitePromotionRook:
4522       case BlackPromotionRook:
4523       case WhitePromotionBishop:
4524       case BlackPromotionBishop:
4525       case WhitePromotionKnight:
4526       case BlackPromotionKnight:
4527       case WhitePromotionKing:
4528       case BlackPromotionKing:
4529       case WhitePromotionChancellor:
4530       case BlackPromotionChancellor:
4531       case WhitePromotionArchbishop:
4532       case BlackPromotionArchbishop:
4533         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4534             sprintf(user_move, "%c%c%c%c=%c\n",
4535                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4536                 PieceToChar(WhiteFerz));
4537         else if(gameInfo.variant == VariantGreat)
4538             sprintf(user_move, "%c%c%c%c=%c\n",
4539                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4540                 PieceToChar(WhiteMan));
4541         else
4542             sprintf(user_move, "%c%c%c%c=%c\n",
4543                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4544                 PieceToChar(PromoPiece(moveType)));
4545         break;
4546       case WhiteDrop:
4547       case BlackDrop:
4548         sprintf(user_move, "%c@%c%c\n",
4549                 ToUpper(PieceToChar((ChessSquare) fromX)),
4550                 AAA + toX, ONE + toY);
4551         break;
4552       case NormalMove:
4553       case WhiteCapturesEnPassant:
4554       case BlackCapturesEnPassant:
4555       case IllegalMove:  /* could be a variant we don't quite understand */
4556         sprintf(user_move, "%c%c%c%c\n",
4557                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4558         break;
4559     }
4560     SendToICS(user_move);
4561     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4562         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4563 }
4564
4565 void
4566 UploadGameEvent()
4567 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4568     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4569     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4570     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4571         DisplayError("You cannot do this while you are playing or observing", 0);
4572         return;
4573     }
4574     if(gameMode != IcsExamining) { // is this ever not the case?
4575         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4576
4577         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4578             sprintf(command, "match %s", ics_handle);
4579         } else { // on FICS we must first go to general examine mode
4580             strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
4581         }
4582         if(gameInfo.variant != VariantNormal) {
4583             // try figure out wild number, as xboard names are not always valid on ICS
4584             for(i=1; i<=36; i++) {
4585                 sprintf(buf, "wild/%d", i);
4586                 if(StringToVariant(buf) == gameInfo.variant) break;
4587             }
4588             if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
4589             else if(i == 22) sprintf(buf, "%s fr\n", command);
4590             else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
4591         } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4592         SendToICS(ics_prefix);
4593         SendToICS(buf);
4594         if(startedFromSetupPosition || backwardMostMove != 0) {
4595           fen = PositionToFEN(backwardMostMove, NULL);
4596           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4597             sprintf(buf, "loadfen %s\n", fen);
4598             SendToICS(buf);
4599           } else { // FICS: everything has to set by separate bsetup commands
4600             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4601             sprintf(buf, "bsetup fen %s\n", fen);
4602             SendToICS(buf);
4603             if(!WhiteOnMove(backwardMostMove)) {
4604                 SendToICS("bsetup tomove black\n");
4605             }
4606             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4607             sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
4608             SendToICS(buf);
4609             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4610             sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
4611             SendToICS(buf);
4612             i = boards[backwardMostMove][EP_STATUS];
4613             if(i >= 0) { // set e.p.
4614                 sprintf(buf, "bsetup eppos %c\n", i+AAA);
4615                 SendToICS(buf);
4616             }
4617             bsetup++;
4618           }
4619         }
4620       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4621             SendToICS("bsetup done\n"); // switch to normal examining.
4622     }
4623     for(i = backwardMostMove; i<last; i++) {
4624         char buf[20];
4625         sprintf(buf, "%s\n", parseList[i]);
4626         SendToICS(buf);
4627     }
4628     SendToICS(ics_prefix);
4629     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4630 }
4631
4632 void
4633 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4634      int rf, ff, rt, ft;
4635      char promoChar;
4636      char move[7];
4637 {
4638     if (rf == DROP_RANK) {
4639         sprintf(move, "%c@%c%c\n",
4640                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4641     } else {
4642         if (promoChar == 'x' || promoChar == NULLCHAR) {
4643             sprintf(move, "%c%c%c%c\n",
4644                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4645         } else {
4646             sprintf(move, "%c%c%c%c%c\n",
4647                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4648         }
4649     }
4650 }
4651
4652 void
4653 ProcessICSInitScript(f)
4654      FILE *f;
4655 {
4656     char buf[MSG_SIZ];
4657
4658     while (fgets(buf, MSG_SIZ, f)) {
4659         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4660     }
4661
4662     fclose(f);
4663 }
4664
4665
4666 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4667 void
4668 AlphaRank(char *move, int n)
4669 {
4670 //    char *p = move, c; int x, y;
4671
4672     if (appData.debugMode) {
4673         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4674     }
4675
4676     if(move[1]=='*' && 
4677        move[2]>='0' && move[2]<='9' &&
4678        move[3]>='a' && move[3]<='x'    ) {
4679         move[1] = '@';
4680         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4681         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4682     } else
4683     if(move[0]>='0' && move[0]<='9' &&
4684        move[1]>='a' && move[1]<='x' &&
4685        move[2]>='0' && move[2]<='9' &&
4686        move[3]>='a' && move[3]<='x'    ) {
4687         /* input move, Shogi -> normal */
4688         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4689         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4690         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4691         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4692     } else
4693     if(move[1]=='@' &&
4694        move[3]>='0' && move[3]<='9' &&
4695        move[2]>='a' && move[2]<='x'    ) {
4696         move[1] = '*';
4697         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4698         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4699     } else
4700     if(
4701        move[0]>='a' && move[0]<='x' &&
4702        move[3]>='0' && move[3]<='9' &&
4703        move[2]>='a' && move[2]<='x'    ) {
4704          /* output move, normal -> Shogi */
4705         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4706         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4707         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4708         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4709         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4710     }
4711     if (appData.debugMode) {
4712         fprintf(debugFP, "   out = '%s'\n", move);
4713     }
4714 }
4715
4716 /* Parser for moves from gnuchess, ICS, or user typein box */
4717 Boolean
4718 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4719      char *move;
4720      int moveNum;
4721      ChessMove *moveType;
4722      int *fromX, *fromY, *toX, *toY;
4723      char *promoChar;
4724 {       
4725     if (appData.debugMode) {
4726         fprintf(debugFP, "move to parse: %s\n", move);
4727     }
4728     *moveType = yylexstr(moveNum, move);
4729
4730     switch (*moveType) {
4731       case WhitePromotionChancellor:
4732       case BlackPromotionChancellor:
4733       case WhitePromotionArchbishop:
4734       case BlackPromotionArchbishop:
4735       case WhitePromotionQueen:
4736       case BlackPromotionQueen:
4737       case WhitePromotionRook:
4738       case BlackPromotionRook:
4739       case WhitePromotionBishop:
4740       case BlackPromotionBishop:
4741       case WhitePromotionKnight:
4742       case BlackPromotionKnight:
4743       case WhitePromotionKing:
4744       case BlackPromotionKing:
4745       case NormalMove:
4746       case WhiteCapturesEnPassant:
4747       case BlackCapturesEnPassant:
4748       case WhiteKingSideCastle:
4749       case WhiteQueenSideCastle:
4750       case BlackKingSideCastle:
4751       case BlackQueenSideCastle:
4752       case WhiteKingSideCastleWild:
4753       case WhiteQueenSideCastleWild:
4754       case BlackKingSideCastleWild:
4755       case BlackQueenSideCastleWild:
4756       /* Code added by Tord: */
4757       case WhiteHSideCastleFR:
4758       case WhiteASideCastleFR:
4759       case BlackHSideCastleFR:
4760       case BlackASideCastleFR:
4761       /* End of code added by Tord */
4762       case IllegalMove:         /* bug or odd chess variant */
4763         *fromX = currentMoveString[0] - AAA;
4764         *fromY = currentMoveString[1] - ONE;
4765         *toX = currentMoveString[2] - AAA;
4766         *toY = currentMoveString[3] - ONE;
4767         *promoChar = currentMoveString[4];
4768         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4769             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4770     if (appData.debugMode) {
4771         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4772     }
4773             *fromX = *fromY = *toX = *toY = 0;
4774             return FALSE;
4775         }
4776         if (appData.testLegality) {
4777           return (*moveType != IllegalMove);
4778         } else {
4779           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
4780                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4781         }
4782
4783       case WhiteDrop:
4784       case BlackDrop:
4785         *fromX = *moveType == WhiteDrop ?
4786           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4787           (int) CharToPiece(ToLower(currentMoveString[0]));
4788         *fromY = DROP_RANK;
4789         *toX = currentMoveString[2] - AAA;
4790         *toY = currentMoveString[3] - ONE;
4791         *promoChar = NULLCHAR;
4792         return TRUE;
4793
4794       case AmbiguousMove:
4795       case ImpossibleMove:
4796       case (ChessMove) 0:       /* end of file */
4797       case ElapsedTime:
4798       case Comment:
4799       case PGNTag:
4800       case NAG:
4801       case WhiteWins:
4802       case BlackWins:
4803       case GameIsDrawn:
4804       default:
4805     if (appData.debugMode) {
4806         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4807     }
4808         /* bug? */
4809         *fromX = *fromY = *toX = *toY = 0;
4810         *promoChar = NULLCHAR;
4811         return FALSE;
4812     }
4813 }
4814
4815
4816 void
4817 ParsePV(char *pv)
4818 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4819   int fromX, fromY, toX, toY; char promoChar;
4820   ChessMove moveType;
4821   Boolean valid;
4822   int nr = 0;
4823
4824   endPV = forwardMostMove;
4825   do {
4826     while(*pv == ' ') pv++;
4827     if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4828     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4829 if(appData.debugMode){
4830 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4831 }
4832     if(!valid && nr == 0 &&
4833        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
4834         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4835         // Hande case where played move is different from leading PV move
4836         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4837         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4838         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4839         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4840           endPV += 2; // if position different, keep this
4841           moveList[endPV-1][0] = fromX + AAA;
4842           moveList[endPV-1][1] = fromY + ONE;
4843           moveList[endPV-1][2] = toX + AAA;
4844           moveList[endPV-1][3] = toY + ONE;
4845           parseList[endPV-1][0] = NULLCHAR;
4846           strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
4847         }
4848       }
4849     }
4850     while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4851     if(moveType == Comment) { valid++; continue; } // allow comments in PV
4852     nr++;
4853     if(endPV+1 > framePtr) break; // no space, truncate
4854     if(!valid) break;
4855     endPV++;
4856     CopyBoard(boards[endPV], boards[endPV-1]);
4857     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4858     moveList[endPV-1][0] = fromX + AAA;
4859     moveList[endPV-1][1] = fromY + ONE;
4860     moveList[endPV-1][2] = toX + AAA;
4861     moveList[endPV-1][3] = toY + ONE;
4862     parseList[endPV-1][0] = NULLCHAR;
4863   } while(valid);
4864   currentMove = endPV;
4865   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4866   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4867                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4868   DrawPosition(TRUE, boards[currentMove]);
4869 }
4870
4871 static int lastX, lastY;
4872
4873 Boolean
4874 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4875 {
4876         int startPV;
4877
4878         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4879         lastX = x; lastY = y;
4880         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4881         startPV = index;
4882       while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4883       index = startPV;
4884         while(buf[index] && buf[index] != '\n') index++;
4885         buf[index] = 0;
4886         ParsePV(buf+startPV);
4887         *start = startPV; *end = index-1;
4888         return TRUE;
4889 }
4890
4891 Boolean
4892 LoadPV(int x, int y)
4893 { // called on right mouse click to load PV
4894   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4895   lastX = x; lastY = y;
4896   ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4897   return TRUE;
4898 }
4899
4900 void
4901 UnLoadPV()
4902 {
4903   if(endPV < 0) return;
4904   endPV = -1;
4905   currentMove = forwardMostMove;
4906   ClearPremoveHighlights();
4907   DrawPosition(TRUE, boards[currentMove]);
4908 }
4909
4910 void
4911 MovePV(int x, int y, int h)
4912 { // step through PV based on mouse coordinates (called on mouse move)
4913   int margin = h>>3, step = 0;
4914
4915   if(endPV < 0) return;
4916   // we must somehow check if right button is still down (might be released off board!)
4917   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4918   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4919   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4920   if(!step) return;
4921   lastX = x; lastY = y;
4922   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4923   currentMove += step;
4924   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4925   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4926                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4927   DrawPosition(FALSE, boards[currentMove]);
4928 }
4929
4930
4931 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4932 // All positions will have equal probability, but the current method will not provide a unique
4933 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4934 #define DARK 1
4935 #define LITE 2
4936 #define ANY 3
4937
4938 int squaresLeft[4];
4939 int piecesLeft[(int)BlackPawn];
4940 int seed, nrOfShuffles;
4941
4942 void GetPositionNumber()
4943 {       // sets global variable seed
4944         int i;
4945
4946         seed = appData.defaultFrcPosition;
4947         if(seed < 0) { // randomize based on time for negative FRC position numbers
4948                 for(i=0; i<50; i++) seed += random();
4949                 seed = random() ^ random() >> 8 ^ random() << 8;
4950                 if(seed<0) seed = -seed;
4951         }
4952 }
4953
4954 int put(Board board, int pieceType, int rank, int n, int shade)
4955 // put the piece on the (n-1)-th empty squares of the given shade
4956 {
4957         int i;
4958
4959         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4960                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4961                         board[rank][i] = (ChessSquare) pieceType;
4962                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4963                         squaresLeft[ANY]--;
4964                         piecesLeft[pieceType]--; 
4965                         return i;
4966                 }
4967         }
4968         return -1;
4969 }
4970
4971
4972 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4973 // calculate where the next piece goes, (any empty square), and put it there
4974 {
4975         int i;
4976
4977         i = seed % squaresLeft[shade];
4978         nrOfShuffles *= squaresLeft[shade];
4979         seed /= squaresLeft[shade];
4980         put(board, pieceType, rank, i, shade);
4981 }
4982
4983 void AddTwoPieces(Board board, int pieceType, int rank)
4984 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4985 {
4986         int i, n=squaresLeft[ANY], j=n-1, k;
4987
4988         k = n*(n-1)/2; // nr of possibilities, not counting permutations
4989         i = seed % k;  // pick one
4990         nrOfShuffles *= k;
4991         seed /= k;
4992         while(i >= j) i -= j--;
4993         j = n - 1 - j; i += j;
4994         put(board, pieceType, rank, j, ANY);
4995         put(board, pieceType, rank, i, ANY);
4996 }
4997
4998 void SetUpShuffle(Board board, int number)
4999 {
5000         int i, p, first=1;
5001
5002         GetPositionNumber(); nrOfShuffles = 1;
5003
5004         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5005         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5006         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5007
5008         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5009
5010         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5011             p = (int) board[0][i];
5012             if(p < (int) BlackPawn) piecesLeft[p] ++;
5013             board[0][i] = EmptySquare;
5014         }
5015
5016         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5017             // shuffles restricted to allow normal castling put KRR first
5018             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5019                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5020             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5021                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5022             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5023                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5024             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5025                 put(board, WhiteRook, 0, 0, ANY);
5026             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5027         }
5028
5029         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5030             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5031             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5032                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5033                 while(piecesLeft[p] >= 2) {
5034                     AddOnePiece(board, p, 0, LITE);
5035                     AddOnePiece(board, p, 0, DARK);
5036                 }
5037                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5038             }
5039
5040         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5041             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5042             // but we leave King and Rooks for last, to possibly obey FRC restriction
5043             if(p == (int)WhiteRook) continue;
5044             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5045             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5046         }
5047
5048         // now everything is placed, except perhaps King (Unicorn) and Rooks
5049
5050         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5051             // Last King gets castling rights
5052             while(piecesLeft[(int)WhiteUnicorn]) {
5053                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5054                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5055             }
5056
5057             while(piecesLeft[(int)WhiteKing]) {
5058                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5059                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5060             }
5061
5062
5063         } else {
5064             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5065             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5066         }
5067
5068         // Only Rooks can be left; simply place them all
5069         while(piecesLeft[(int)WhiteRook]) {
5070                 i = put(board, WhiteRook, 0, 0, ANY);
5071                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5072                         if(first) {
5073                                 first=0;
5074                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5075                         }
5076                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5077                 }
5078         }
5079         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5080             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5081         }
5082
5083         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5084 }
5085
5086 int SetCharTable( char *table, const char * map )
5087 /* [HGM] moved here from winboard.c because of its general usefulness */
5088 /*       Basically a safe strcpy that uses the last character as King */
5089 {
5090     int result = FALSE; int NrPieces;
5091
5092     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare 
5093                     && NrPieces >= 12 && !(NrPieces&1)) {
5094         int i; /* [HGM] Accept even length from 12 to 34 */
5095
5096         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5097         for( i=0; i<NrPieces/2-1; i++ ) {
5098             table[i] = map[i];
5099             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5100         }
5101         table[(int) WhiteKing]  = map[NrPieces/2-1];
5102         table[(int) BlackKing]  = map[NrPieces-1];
5103
5104         result = TRUE;
5105     }
5106
5107     return result;
5108 }
5109
5110 void Prelude(Board board)
5111 {       // [HGM] superchess: random selection of exo-pieces
5112         int i, j, k; ChessSquare p; 
5113         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5114
5115         GetPositionNumber(); // use FRC position number
5116
5117         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5118             SetCharTable(pieceToChar, appData.pieceToCharTable);
5119             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) 
5120                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5121         }
5122
5123         j = seed%4;                 seed /= 4; 
5124         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5125         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5126         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5127         j = seed%3 + (seed%3 >= j); seed /= 3; 
5128         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5129         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5130         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5131         j = seed%3;                 seed /= 3; 
5132         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5133         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5134         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5135         j = seed%2 + (seed%2 >= j); seed /= 2; 
5136         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5137         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5138         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5139         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5140         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5141         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5142         put(board, exoPieces[0],    0, 0, ANY);
5143         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5144 }
5145
5146 void
5147 InitPosition(redraw)
5148      int redraw;
5149 {
5150     ChessSquare (* pieces)[BOARD_FILES];
5151     int i, j, pawnRow, overrule,
5152     oldx = gameInfo.boardWidth,
5153     oldy = gameInfo.boardHeight,
5154     oldh = gameInfo.holdingsWidth,
5155     oldv = gameInfo.variant;
5156
5157     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5158
5159     /* [AS] Initialize pv info list [HGM] and game status */
5160     {
5161         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5162             pvInfoList[i].depth = 0;
5163             boards[i][EP_STATUS] = EP_NONE;
5164             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5165         }
5166
5167         initialRulePlies = 0; /* 50-move counter start */
5168
5169         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5170         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5171     }
5172
5173     
5174     /* [HGM] logic here is completely changed. In stead of full positions */
5175     /* the initialized data only consist of the two backranks. The switch */
5176     /* selects which one we will use, which is than copied to the Board   */
5177     /* initialPosition, which for the rest is initialized by Pawns and    */
5178     /* empty squares. This initial position is then copied to boards[0],  */
5179     /* possibly after shuffling, so that it remains available.            */
5180
5181     gameInfo.holdingsWidth = 0; /* default board sizes */
5182     gameInfo.boardWidth    = 8;
5183     gameInfo.boardHeight   = 8;
5184     gameInfo.holdingsSize  = 0;
5185     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5186     for(i=0; i<BOARD_FILES-2; i++)
5187       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5188     initialPosition[EP_STATUS] = EP_NONE;
5189     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
5190
5191     switch (gameInfo.variant) {
5192     case VariantFischeRandom:
5193       shuffleOpenings = TRUE;
5194     default:
5195       pieces = FIDEArray;
5196       break;
5197     case VariantShatranj:
5198       pieces = ShatranjArray;
5199       nrCastlingRights = 0;
5200       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); 
5201       break;
5202     case VariantMakruk:
5203       pieces = makrukArray;
5204       nrCastlingRights = 0;
5205       startedFromSetupPosition = TRUE;
5206       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); 
5207       break;
5208     case VariantTwoKings:
5209       pieces = twoKingsArray;
5210       break;
5211     case VariantCapaRandom:
5212       shuffleOpenings = TRUE;
5213     case VariantCapablanca:
5214       pieces = CapablancaArray;
5215       gameInfo.boardWidth = 10;
5216       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5217       break;
5218     case VariantGothic:
5219       pieces = GothicArray;
5220       gameInfo.boardWidth = 10;
5221       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); 
5222       break;
5223     case VariantJanus:
5224       pieces = JanusArray;
5225       gameInfo.boardWidth = 10;
5226       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); 
5227       nrCastlingRights = 6;
5228         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5229         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5230         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5231         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5232         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5233         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5234       break;
5235     case VariantFalcon:
5236       pieces = FalconArray;
5237       gameInfo.boardWidth = 10;
5238       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); 
5239       break;
5240     case VariantXiangqi:
5241       pieces = XiangqiArray;
5242       gameInfo.boardWidth  = 9;
5243       gameInfo.boardHeight = 10;
5244       nrCastlingRights = 0;
5245       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); 
5246       break;
5247     case VariantShogi:
5248       pieces = ShogiArray;
5249       gameInfo.boardWidth  = 9;
5250       gameInfo.boardHeight = 9;
5251       gameInfo.holdingsSize = 7;
5252       nrCastlingRights = 0;
5253       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); 
5254       break;
5255     case VariantCourier:
5256       pieces = CourierArray;
5257       gameInfo.boardWidth  = 12;
5258       nrCastlingRights = 0;
5259       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); 
5260       break;
5261     case VariantKnightmate:
5262       pieces = KnightmateArray;
5263       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); 
5264       break;
5265     case VariantFairy:
5266       pieces = fairyArray;
5267       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); 
5268       break;
5269     case VariantGreat:
5270       pieces = GreatArray;
5271       gameInfo.boardWidth = 10;
5272       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5273       gameInfo.holdingsSize = 8;
5274       break;
5275     case VariantSuper:
5276       pieces = FIDEArray;
5277       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5278       gameInfo.holdingsSize = 8;
5279       startedFromSetupPosition = TRUE;
5280       break;
5281     case VariantCrazyhouse:
5282     case VariantBughouse:
5283       pieces = FIDEArray;
5284       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); 
5285       gameInfo.holdingsSize = 5;
5286       break;
5287     case VariantWildCastle:
5288       pieces = FIDEArray;
5289       /* !!?shuffle with kings guaranteed to be on d or e file */
5290       shuffleOpenings = 1;
5291       break;
5292     case VariantNoCastle:
5293       pieces = FIDEArray;
5294       nrCastlingRights = 0;
5295       /* !!?unconstrained back-rank shuffle */
5296       shuffleOpenings = 1;
5297       break;
5298     }
5299
5300     overrule = 0;
5301     if(appData.NrFiles >= 0) {
5302         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5303         gameInfo.boardWidth = appData.NrFiles;
5304     }
5305     if(appData.NrRanks >= 0) {
5306         gameInfo.boardHeight = appData.NrRanks;
5307     }
5308     if(appData.holdingsSize >= 0) {
5309         i = appData.holdingsSize;
5310         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5311         gameInfo.holdingsSize = i;
5312     }
5313     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5314     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5315         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5316
5317     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5318     if(pawnRow < 1) pawnRow = 1;
5319     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5320
5321     /* User pieceToChar list overrules defaults */
5322     if(appData.pieceToCharTable != NULL)
5323         SetCharTable(pieceToChar, appData.pieceToCharTable);
5324
5325     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5326
5327         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5328             s = (ChessSquare) 0; /* account holding counts in guard band */
5329         for( i=0; i<BOARD_HEIGHT; i++ )
5330             initialPosition[i][j] = s;
5331
5332         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5333         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5334         initialPosition[pawnRow][j] = WhitePawn;
5335         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5336         if(gameInfo.variant == VariantXiangqi) {
5337             if(j&1) {
5338                 initialPosition[pawnRow][j] = 
5339                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5340                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5341                    initialPosition[2][j] = WhiteCannon;
5342                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5343                 }
5344             }
5345         }
5346         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5347     }
5348     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5349
5350             j=BOARD_LEFT+1;
5351             initialPosition[1][j] = WhiteBishop;
5352             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5353             j=BOARD_RGHT-2;
5354             initialPosition[1][j] = WhiteRook;
5355             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5356     }
5357
5358     if( nrCastlingRights == -1) {
5359         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5360         /*       This sets default castling rights from none to normal corners   */
5361         /* Variants with other castling rights must set them themselves above    */
5362         nrCastlingRights = 6;
5363        
5364         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5365         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5366         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5367         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5368         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5369         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5370      }
5371
5372      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5373      if(gameInfo.variant == VariantGreat) { // promotion commoners
5374         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5375         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5376         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5377         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5378      }
5379   if (appData.debugMode) {
5380     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5381   }
5382     if(shuffleOpenings) {
5383         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5384         startedFromSetupPosition = TRUE;
5385     }
5386     if(startedFromPositionFile) {
5387       /* [HGM] loadPos: use PositionFile for every new game */
5388       CopyBoard(initialPosition, filePosition);
5389       for(i=0; i<nrCastlingRights; i++)
5390           initialRights[i] = filePosition[CASTLING][i];
5391       startedFromSetupPosition = TRUE;
5392     }
5393
5394     CopyBoard(boards[0], initialPosition);
5395
5396     if(oldx != gameInfo.boardWidth ||
5397        oldy != gameInfo.boardHeight ||
5398        oldh != gameInfo.holdingsWidth
5399 #ifdef GOTHIC
5400        || oldv == VariantGothic ||        // For licensing popups
5401        gameInfo.variant == VariantGothic
5402 #endif
5403 #ifdef FALCON
5404        || oldv == VariantFalcon ||
5405        gameInfo.variant == VariantFalcon
5406 #endif
5407                                          )
5408             InitDrawingSizes(-2 ,0);
5409
5410     if (redraw)
5411       DrawPosition(TRUE, boards[currentMove]);
5412 }
5413
5414 void
5415 SendBoard(cps, moveNum)
5416      ChessProgramState *cps;
5417      int moveNum;
5418 {
5419     char message[MSG_SIZ];
5420     
5421     if (cps->useSetboard) {
5422       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5423       sprintf(message, "setboard %s\n", fen);
5424       SendToProgram(message, cps);
5425       free(fen);
5426
5427     } else {
5428       ChessSquare *bp;
5429       int i, j;
5430       /* Kludge to set black to move, avoiding the troublesome and now
5431        * deprecated "black" command.
5432        */
5433       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5434
5435       SendToProgram("edit\n", cps);
5436       SendToProgram("#\n", cps);
5437       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5438         bp = &boards[moveNum][i][BOARD_LEFT];
5439         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5440           if ((int) *bp < (int) BlackPawn) {
5441             sprintf(message, "%c%c%c\n", PieceToChar(*bp), 
5442                     AAA + j, ONE + i);
5443             if(message[0] == '+' || message[0] == '~') {
5444                 sprintf(message, "%c%c%c+\n",
5445                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5446                         AAA + j, ONE + i);
5447             }
5448             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5449                 message[1] = BOARD_RGHT   - 1 - j + '1';
5450                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5451             }
5452             SendToProgram(message, cps);
5453           }
5454         }
5455       }
5456     
5457       SendToProgram("c\n", cps);
5458       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5459         bp = &boards[moveNum][i][BOARD_LEFT];
5460         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5461           if (((int) *bp != (int) EmptySquare)
5462               && ((int) *bp >= (int) BlackPawn)) {
5463             sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5464                     AAA + j, ONE + i);
5465             if(message[0] == '+' || message[0] == '~') {
5466                 sprintf(message, "%c%c%c+\n",
5467                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5468                         AAA + j, ONE + i);
5469             }
5470             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5471                 message[1] = BOARD_RGHT   - 1 - j + '1';
5472                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5473             }
5474             SendToProgram(message, cps);
5475           }
5476         }
5477       }
5478     
5479       SendToProgram(".\n", cps);
5480     }
5481     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5482 }
5483
5484 static int autoQueen; // [HGM] oneclick
5485
5486 int
5487 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5488 {
5489     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5490     /* [HGM] add Shogi promotions */
5491     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5492     ChessSquare piece;
5493     ChessMove moveType;
5494     Boolean premove;
5495
5496     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5497     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5498
5499     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5500       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5501         return FALSE;
5502
5503     piece = boards[currentMove][fromY][fromX];
5504     if(gameInfo.variant == VariantShogi) {
5505         promotionZoneSize = 3;
5506         highestPromotingPiece = (int)WhiteFerz;
5507     } else if(gameInfo.variant == VariantMakruk) {
5508         promotionZoneSize = 3;
5509     }
5510
5511     // next weed out all moves that do not touch the promotion zone at all
5512     if((int)piece >= BlackPawn) {
5513         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5514              return FALSE;
5515         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5516     } else {
5517         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5518            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5519     }
5520
5521     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5522
5523     // weed out mandatory Shogi promotions
5524     if(gameInfo.variant == VariantShogi) {
5525         if(piece >= BlackPawn) {
5526             if(toY == 0 && piece == BlackPawn ||
5527                toY == 0 && piece == BlackQueen ||
5528                toY <= 1 && piece == BlackKnight) {
5529                 *promoChoice = '+';
5530                 return FALSE;
5531             }
5532         } else {
5533             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5534                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5535                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5536                 *promoChoice = '+';
5537                 return FALSE;
5538             }
5539         }
5540     }
5541
5542     // weed out obviously illegal Pawn moves
5543     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5544         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5545         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5546         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5547         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5548         // note we are not allowed to test for valid (non-)capture, due to premove
5549     }
5550
5551     // we either have a choice what to promote to, or (in Shogi) whether to promote
5552     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5553         *promoChoice = PieceToChar(BlackFerz);  // no choice
5554         return FALSE;
5555     }
5556     if(autoQueen) { // predetermined
5557         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5558              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5559         else *promoChoice = PieceToChar(BlackQueen);
5560         return FALSE;
5561     }
5562
5563     // suppress promotion popup on illegal moves that are not premoves
5564     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5565               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5566     if(appData.testLegality && !premove) {
5567         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5568                         fromY, fromX, toY, toX, NULLCHAR);
5569         if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
5570            moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5571             return FALSE;
5572     }
5573
5574     return TRUE;
5575 }
5576
5577 int
5578 InPalace(row, column)
5579      int row, column;
5580 {   /* [HGM] for Xiangqi */
5581     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5582          column < (BOARD_WIDTH + 4)/2 &&
5583          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5584     return FALSE;
5585 }
5586
5587 int
5588 PieceForSquare (x, y)
5589      int x;
5590      int y;
5591 {
5592   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5593      return -1;
5594   else
5595      return boards[currentMove][y][x];
5596 }
5597
5598 int
5599 OKToStartUserMove(x, y)
5600      int x, y;
5601 {
5602     ChessSquare from_piece;
5603     int white_piece;
5604
5605     if (matchMode) return FALSE;
5606     if (gameMode == EditPosition) return TRUE;
5607
5608     if (x >= 0 && y >= 0)
5609       from_piece = boards[currentMove][y][x];
5610     else
5611       from_piece = EmptySquare;
5612
5613     if (from_piece == EmptySquare) return FALSE;
5614
5615     white_piece = (int)from_piece >= (int)WhitePawn &&
5616       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5617
5618     switch (gameMode) {
5619       case PlayFromGameFile:
5620       case AnalyzeFile:
5621       case TwoMachinesPlay:
5622       case EndOfGame:
5623         return FALSE;
5624
5625       case IcsObserving:
5626       case IcsIdle:
5627         return FALSE;
5628
5629       case MachinePlaysWhite:
5630       case IcsPlayingBlack:
5631         if (appData.zippyPlay) return FALSE;
5632         if (white_piece) {
5633             DisplayMoveError(_("You are playing Black"));
5634             return FALSE;
5635         }
5636         break;
5637
5638       case MachinePlaysBlack:
5639       case IcsPlayingWhite:
5640         if (appData.zippyPlay) return FALSE;
5641         if (!white_piece) {
5642             DisplayMoveError(_("You are playing White"));
5643             return FALSE;
5644         }
5645         break;
5646
5647       case EditGame:
5648         if (!white_piece && WhiteOnMove(currentMove)) {
5649             DisplayMoveError(_("It is White's turn"));
5650             return FALSE;
5651         }           
5652         if (white_piece && !WhiteOnMove(currentMove)) {
5653             DisplayMoveError(_("It is Black's turn"));
5654             return FALSE;
5655         }           
5656         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5657             /* Editing correspondence game history */
5658             /* Could disallow this or prompt for confirmation */
5659             cmailOldMove = -1;
5660         }
5661         break;
5662
5663       case BeginningOfGame:
5664         if (appData.icsActive) return FALSE;
5665         if (!appData.noChessProgram) {
5666             if (!white_piece) {
5667                 DisplayMoveError(_("You are playing White"));
5668                 return FALSE;
5669             }
5670         }
5671         break;
5672         
5673       case Training:
5674         if (!white_piece && WhiteOnMove(currentMove)) {
5675             DisplayMoveError(_("It is White's turn"));
5676             return FALSE;
5677         }           
5678         if (white_piece && !WhiteOnMove(currentMove)) {
5679             DisplayMoveError(_("It is Black's turn"));
5680             return FALSE;
5681         }           
5682         break;
5683
5684       default:
5685       case IcsExamining:
5686         break;
5687     }
5688     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5689         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5690         && gameMode != AnalyzeFile && gameMode != Training) {
5691         DisplayMoveError(_("Displayed position is not current"));
5692         return FALSE;
5693     }
5694     return TRUE;
5695 }
5696
5697 Boolean
5698 OnlyMove(int *x, int *y, Boolean captures) {
5699     DisambiguateClosure cl;
5700     if (appData.zippyPlay) return FALSE;
5701     switch(gameMode) {
5702       case MachinePlaysBlack:
5703       case IcsPlayingWhite:
5704       case BeginningOfGame:
5705         if(!WhiteOnMove(currentMove)) return FALSE;
5706         break;
5707       case MachinePlaysWhite:
5708       case IcsPlayingBlack:
5709         if(WhiteOnMove(currentMove)) return FALSE;
5710         break;
5711       default:
5712         return FALSE;
5713     }
5714     cl.pieceIn = EmptySquare; 
5715     cl.rfIn = *y;
5716     cl.ffIn = *x;
5717     cl.rtIn = -1;
5718     cl.ftIn = -1;
5719     cl.promoCharIn = NULLCHAR;
5720     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5721     if( cl.kind == NormalMove ||
5722         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5723         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5724         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5725         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5726       fromX = cl.ff;
5727       fromY = cl.rf;
5728       *x = cl.ft;
5729       *y = cl.rt;
5730       return TRUE;
5731     }
5732     if(cl.kind != ImpossibleMove) return FALSE;
5733     cl.pieceIn = EmptySquare;
5734     cl.rfIn = -1;
5735     cl.ffIn = -1;
5736     cl.rtIn = *y;
5737     cl.ftIn = *x;
5738     cl.promoCharIn = NULLCHAR;
5739     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5740     if( cl.kind == NormalMove ||
5741         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5742         cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
5743         cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
5744         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5745       fromX = cl.ff;
5746       fromY = cl.rf;
5747       *x = cl.ft;
5748       *y = cl.rt;
5749       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5750       return TRUE;
5751     }
5752     return FALSE;
5753 }
5754
5755 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5756 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5757 int lastLoadGameUseList = FALSE;
5758 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5759 ChessMove lastLoadGameStart = (ChessMove) 0;
5760
5761 ChessMove
5762 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5763      int fromX, fromY, toX, toY;
5764      int promoChar;
5765      Boolean captureOwn;
5766 {
5767     ChessMove moveType;
5768     ChessSquare pdown, pup;
5769
5770     /* Check if the user is playing in turn.  This is complicated because we
5771        let the user "pick up" a piece before it is his turn.  So the piece he
5772        tried to pick up may have been captured by the time he puts it down!
5773        Therefore we use the color the user is supposed to be playing in this
5774        test, not the color of the piece that is currently on the starting
5775        square---except in EditGame mode, where the user is playing both
5776        sides; fortunately there the capture race can't happen.  (It can
5777        now happen in IcsExamining mode, but that's just too bad.  The user
5778        will get a somewhat confusing message in that case.)
5779        */
5780
5781     switch (gameMode) {
5782       case PlayFromGameFile:
5783       case AnalyzeFile:
5784       case TwoMachinesPlay:
5785       case EndOfGame:
5786       case IcsObserving:
5787       case IcsIdle:
5788         /* We switched into a game mode where moves are not accepted,
5789            perhaps while the mouse button was down. */
5790         return ImpossibleMove;
5791
5792       case MachinePlaysWhite:
5793         /* User is moving for Black */
5794         if (WhiteOnMove(currentMove)) {
5795             DisplayMoveError(_("It is White's turn"));
5796             return ImpossibleMove;
5797         }
5798         break;
5799
5800       case MachinePlaysBlack:
5801         /* User is moving for White */
5802         if (!WhiteOnMove(currentMove)) {
5803             DisplayMoveError(_("It is Black's turn"));
5804             return ImpossibleMove;
5805         }
5806         break;
5807
5808       case EditGame:
5809       case IcsExamining:
5810       case BeginningOfGame:
5811       case AnalyzeMode:
5812       case Training:
5813         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5814             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5815             /* User is moving for Black */
5816             if (WhiteOnMove(currentMove)) {
5817                 DisplayMoveError(_("It is White's turn"));
5818                 return ImpossibleMove;
5819             }
5820         } else {
5821             /* User is moving for White */
5822             if (!WhiteOnMove(currentMove)) {
5823                 DisplayMoveError(_("It is Black's turn"));
5824                 return ImpossibleMove;
5825             }
5826         }
5827         break;
5828
5829       case IcsPlayingBlack:
5830         /* User is moving for Black */
5831         if (WhiteOnMove(currentMove)) {
5832             if (!appData.premove) {
5833                 DisplayMoveError(_("It is White's turn"));
5834             } else if (toX >= 0 && toY >= 0) {
5835                 premoveToX = toX;
5836                 premoveToY = toY;
5837                 premoveFromX = fromX;
5838                 premoveFromY = fromY;
5839                 premovePromoChar = promoChar;
5840                 gotPremove = 1;
5841                 if (appData.debugMode) 
5842                     fprintf(debugFP, "Got premove: fromX %d,"
5843                             "fromY %d, toX %d, toY %d\n",
5844                             fromX, fromY, toX, toY);
5845             }
5846             return ImpossibleMove;
5847         }
5848         break;
5849
5850       case IcsPlayingWhite:
5851         /* User is moving for White */
5852         if (!WhiteOnMove(currentMove)) {
5853             if (!appData.premove) {
5854                 DisplayMoveError(_("It is Black's turn"));
5855             } else if (toX >= 0 && toY >= 0) {
5856                 premoveToX = toX;
5857                 premoveToY = toY;
5858                 premoveFromX = fromX;
5859                 premoveFromY = fromY;
5860                 premovePromoChar = promoChar;
5861                 gotPremove = 1;
5862                 if (appData.debugMode) 
5863                     fprintf(debugFP, "Got premove: fromX %d,"
5864                             "fromY %d, toX %d, toY %d\n",
5865                             fromX, fromY, toX, toY);
5866             }
5867             return ImpossibleMove;
5868         }
5869         break;
5870
5871       default:
5872         break;
5873
5874       case EditPosition:
5875         /* EditPosition, empty square, or different color piece;
5876            click-click move is possible */
5877         if (toX == -2 || toY == -2) {
5878             boards[0][fromY][fromX] = EmptySquare;
5879             return AmbiguousMove;
5880         } else if (toX >= 0 && toY >= 0) {
5881             boards[0][toY][toX] = boards[0][fromY][fromX];
5882             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5883                 if(boards[0][fromY][0] != EmptySquare) {
5884                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
5885                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
5886                 }
5887             } else
5888             if(fromX == BOARD_RGHT+1) {
5889                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5890                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5891                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
5892                 }
5893             } else
5894             boards[0][fromY][fromX] = EmptySquare;
5895             return AmbiguousMove;
5896         }
5897         return ImpossibleMove;
5898     }
5899
5900     if(toX < 0 || toY < 0) return ImpossibleMove;
5901     pdown = boards[currentMove][fromY][fromX];
5902     pup = boards[currentMove][toY][toX];
5903
5904     /* [HGM] If move started in holdings, it means a drop */
5905     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
5906          if( pup != EmptySquare ) return ImpossibleMove;
5907          if(appData.testLegality) {
5908              /* it would be more logical if LegalityTest() also figured out
5909               * which drops are legal. For now we forbid pawns on back rank.
5910               * Shogi is on its own here...
5911               */
5912              if( (pdown == WhitePawn || pdown == BlackPawn) &&
5913                  (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5914                  return(ImpossibleMove); /* no pawn drops on 1st/8th */
5915          }
5916          return WhiteDrop; /* Not needed to specify white or black yet */
5917     }
5918
5919     /* [HGM] always test for legality, to get promotion info */
5920     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5921                                          fromY, fromX, toY, toX, promoChar);
5922     /* [HGM] but possibly ignore an IllegalMove result */
5923     if (appData.testLegality) {
5924         if (moveType == IllegalMove || moveType == ImpossibleMove) {
5925             DisplayMoveError(_("Illegal move"));
5926             return ImpossibleMove;
5927         }
5928     }
5929
5930     return moveType;
5931     /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5932        function is made into one that returns an OK move type if FinishMove
5933        should be called. This to give the calling driver routine the
5934        opportunity to finish the userMove input with a promotion popup,
5935        without bothering the user with this for invalid or illegal moves */
5936
5937 /*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5938 }
5939
5940 /* Common tail of UserMoveEvent and DropMenuEvent */
5941 int
5942 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5943      ChessMove moveType;
5944      int fromX, fromY, toX, toY;
5945      /*char*/int promoChar;
5946 {
5947     char *bookHit = 0;
5948
5949     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { 
5950         // [HGM] superchess: suppress promotions to non-available piece
5951         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5952         if(WhiteOnMove(currentMove)) {
5953             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5954         } else {
5955             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5956         }
5957     }
5958
5959     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5960        move type in caller when we know the move is a legal promotion */
5961     if(moveType == NormalMove && promoChar)
5962         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5963
5964     /* [HGM] convert drag-and-drop piece drops to standard form */
5965     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5966          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5967            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
5968                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5969            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5970            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5971            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5972            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
5973          fromY = DROP_RANK;
5974     }
5975
5976     /* [HGM] <popupFix> The following if has been moved here from
5977        UserMoveEvent(). Because it seemed to belong here (why not allow
5978        piece drops in training games?), and because it can only be
5979        performed after it is known to what we promote. */
5980     if (gameMode == Training) {
5981       /* compare the move played on the board to the next move in the
5982        * game. If they match, display the move and the opponent's response. 
5983        * If they don't match, display an error message.
5984        */
5985       int saveAnimate;
5986       Board testBoard;
5987       CopyBoard(testBoard, boards[currentMove]);
5988       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5989
5990       if (CompareBoards(testBoard, boards[currentMove+1])) {
5991         ForwardInner(currentMove+1);
5992
5993         /* Autoplay the opponent's response.
5994          * if appData.animate was TRUE when Training mode was entered,
5995          * the response will be animated.
5996          */
5997         saveAnimate = appData.animate;
5998         appData.animate = animateTraining;
5999         ForwardInner(currentMove+1);
6000         appData.animate = saveAnimate;
6001
6002         /* check for the end of the game */
6003         if (currentMove >= forwardMostMove) {
6004           gameMode = PlayFromGameFile;
6005           ModeHighlight();
6006           SetTrainingModeOff();
6007           DisplayInformation(_("End of game"));
6008         }
6009       } else {
6010         DisplayError(_("Incorrect move"), 0);
6011       }
6012       return 1;
6013     }
6014
6015   /* Ok, now we know that the move is good, so we can kill
6016      the previous line in Analysis Mode */
6017   if ((gameMode == AnalyzeMode || gameMode == EditGame) 
6018                                 && currentMove < forwardMostMove) {
6019     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6020   }
6021
6022   /* If we need the chess program but it's dead, restart it */
6023   ResurrectChessProgram();
6024
6025   /* A user move restarts a paused game*/
6026   if (pausing)
6027     PauseEvent();
6028
6029   thinkOutput[0] = NULLCHAR;
6030
6031   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6032
6033   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6034
6035   if (gameMode == BeginningOfGame) {
6036     if (appData.noChessProgram) {
6037       gameMode = EditGame;
6038       SetGameInfo();
6039     } else {
6040       char buf[MSG_SIZ];
6041       gameMode = MachinePlaysBlack;
6042       StartClocks();
6043       SetGameInfo();
6044       sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
6045       DisplayTitle(buf);
6046       if (first.sendName) {
6047         sprintf(buf, "name %s\n", gameInfo.white);
6048         SendToProgram(buf, &first);
6049       }
6050       StartClocks();
6051     }
6052     ModeHighlight();
6053   }
6054
6055   /* Relay move to ICS or chess engine */
6056   if (appData.icsActive) {
6057     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6058         gameMode == IcsExamining) {
6059       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6060         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6061         SendToICS("draw ");
6062         SendMoveToICS(moveType, fromX, fromY, toX, toY);
6063       }
6064       // also send plain move, in case ICS does not understand atomic claims
6065       SendMoveToICS(moveType, fromX, fromY, toX, toY);
6066       ics_user_moved = 1;
6067     }
6068   } else {
6069     if (first.sendTime && (gameMode == BeginningOfGame ||
6070                            gameMode == MachinePlaysWhite ||
6071                            gameMode == MachinePlaysBlack)) {
6072       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6073     }
6074     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6075          // [HGM] book: if program might be playing, let it use book
6076         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6077         first.maybeThinking = TRUE;
6078     } else SendMoveToProgram(forwardMostMove-1, &first);
6079     if (currentMove == cmailOldMove + 1) {
6080       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6081     }
6082   }
6083
6084   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085
6086   switch (gameMode) {
6087   case EditGame:
6088     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6089     case MT_NONE:
6090     case MT_CHECK:
6091       break;
6092     case MT_CHECKMATE:
6093     case MT_STAINMATE:
6094       if (WhiteOnMove(currentMove)) {
6095         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6096       } else {
6097         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6098       }
6099       break;
6100     case MT_STALEMATE:
6101       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6102       break;
6103     }
6104     break;
6105     
6106   case MachinePlaysBlack:
6107   case MachinePlaysWhite:
6108     /* disable certain menu options while machine is thinking */
6109     SetMachineThinkingEnables();
6110     break;
6111
6112   default:
6113     break;
6114   }
6115
6116   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6117         
6118   if(bookHit) { // [HGM] book: simulate book reply
6119         static char bookMove[MSG_SIZ]; // a bit generous?
6120
6121         programStats.nodes = programStats.depth = programStats.time = 
6122         programStats.score = programStats.got_only_move = 0;
6123         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6124
6125         strcpy(bookMove, "move ");
6126         strcat(bookMove, bookHit);
6127         HandleMachineMove(bookMove, &first);
6128   }
6129   return 1;
6130 }
6131
6132 void
6133 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6134      int fromX, fromY, toX, toY;
6135      int promoChar;
6136 {
6137     /* [HGM] This routine was added to allow calling of its two logical
6138        parts from other modules in the old way. Before, UserMoveEvent()
6139        automatically called FinishMove() if the move was OK, and returned
6140        otherwise. I separated the two, in order to make it possible to
6141        slip a promotion popup in between. But that it always needs two
6142        calls, to the first part, (now called UserMoveTest() ), and to
6143        FinishMove if the first part succeeded. Calls that do not need
6144        to do anything in between, can call this routine the old way. 
6145     */
6146     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
6147 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
6148     if(moveType == AmbiguousMove)
6149         DrawPosition(FALSE, boards[currentMove]);
6150     else if(moveType != ImpossibleMove && moveType != Comment)
6151         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6152 }
6153
6154 void
6155 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6156      Board board;
6157      int flags;
6158      ChessMove kind;
6159      int rf, ff, rt, ft;
6160      VOIDSTAR closure;
6161 {
6162     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6163     Markers *m = (Markers *) closure;
6164     if(rf == fromY && ff == fromX)
6165         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6166                          || kind == WhiteCapturesEnPassant
6167                          || kind == BlackCapturesEnPassant);
6168     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6169 }
6170
6171 void
6172 MarkTargetSquares(int clear)
6173 {
6174   int x, y;
6175   if(!appData.markers || !appData.highlightDragging || 
6176      !appData.testLegality || gameMode == EditPosition) return;
6177   if(clear) {
6178     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6179   } else {
6180     int capt = 0;
6181     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6182     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6183       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6184       if(capt)
6185       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6186     }
6187   }
6188   DrawPosition(TRUE, NULL);
6189 }
6190
6191 void LeftClick(ClickType clickType, int xPix, int yPix)
6192 {
6193     int x, y;
6194     Boolean saveAnimate;
6195     static int second = 0, promotionChoice = 0;
6196     char promoChoice = NULLCHAR;
6197
6198     if(appData.seekGraph && appData.icsActive && loggedOn &&
6199         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6200         SeekGraphClick(clickType, xPix, yPix, 0);
6201         return;
6202     }
6203
6204     if (clickType == Press) ErrorPopDown();
6205     MarkTargetSquares(1);
6206
6207     x = EventToSquare(xPix, BOARD_WIDTH);
6208     y = EventToSquare(yPix, BOARD_HEIGHT);
6209     if (!flipView && y >= 0) {
6210         y = BOARD_HEIGHT - 1 - y;
6211     }
6212     if (flipView && x >= 0) {
6213         x = BOARD_WIDTH - 1 - x;
6214     }
6215
6216     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6217         if(clickType == Release) return; // ignore upclick of click-click destination
6218         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6219         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6220         if(gameInfo.holdingsWidth && 
6221                 (WhiteOnMove(currentMove) 
6222                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6223                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6224             // click in right holdings, for determining promotion piece
6225             ChessSquare p = boards[currentMove][y][x];
6226             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6227             if(p != EmptySquare) {
6228                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6229                 fromX = fromY = -1;
6230                 return;
6231             }
6232         }
6233         DrawPosition(FALSE, boards[currentMove]);
6234         return;
6235     }
6236
6237     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6238     if(clickType == Press
6239             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6240               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6241               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6242         return;
6243
6244     autoQueen = appData.alwaysPromoteToQueen;
6245
6246     if (fromX == -1) {
6247       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6248         if (clickType == Press) {
6249             /* First square */
6250             if (OKToStartUserMove(x, y)) {
6251                 fromX = x;
6252                 fromY = y;
6253                 second = 0;
6254                 MarkTargetSquares(0);
6255                 DragPieceBegin(xPix, yPix);
6256                 if (appData.highlightDragging) {
6257                     SetHighlights(x, y, -1, -1);
6258                 }
6259             }
6260         }
6261         return;
6262       }
6263     }
6264
6265     /* fromX != -1 */
6266     if (clickType == Press && gameMode != EditPosition) {
6267         ChessSquare fromP;
6268         ChessSquare toP;
6269         int frc;
6270
6271         // ignore off-board to clicks
6272         if(y < 0 || x < 0) return;
6273
6274         /* Check if clicking again on the same color piece */
6275         fromP = boards[currentMove][fromY][fromX];
6276         toP = boards[currentMove][y][x];
6277         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6278         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6279              WhitePawn <= toP && toP <= WhiteKing &&
6280              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6281              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6282             (BlackPawn <= fromP && fromP <= BlackKing && 
6283              BlackPawn <= toP && toP <= BlackKing &&
6284              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6285              !(fromP == BlackKing && toP == BlackRook && frc))) {
6286             /* Clicked again on same color piece -- changed his mind */
6287             second = (x == fromX && y == fromY);
6288            if(!second || !OnlyMove(&x, &y, TRUE)) {
6289             if (appData.highlightDragging) {
6290                 SetHighlights(x, y, -1, -1);
6291             } else {
6292                 ClearHighlights();
6293             }
6294             if (OKToStartUserMove(x, y)) {
6295                 fromX = x;
6296                 fromY = y;
6297                 MarkTargetSquares(0);
6298                 DragPieceBegin(xPix, yPix);
6299             }
6300             return;
6301            }
6302         }
6303         // ignore clicks on holdings
6304         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6305     }
6306
6307     if (clickType == Release && x == fromX && y == fromY) {
6308         DragPieceEnd(xPix, yPix);
6309         if (appData.animateDragging) {
6310             /* Undo animation damage if any */
6311             DrawPosition(FALSE, NULL);
6312         }
6313         if (second) {
6314             /* Second up/down in same square; just abort move */
6315             second = 0;
6316             fromX = fromY = -1;
6317             ClearHighlights();
6318             gotPremove = 0;
6319             ClearPremoveHighlights();
6320         } else {
6321             /* First upclick in same square; start click-click mode */
6322             SetHighlights(x, y, -1, -1);
6323         }
6324         return;
6325     }
6326
6327     /* we now have a different from- and (possibly off-board) to-square */
6328     /* Completed move */
6329     toX = x;
6330     toY = y;
6331     saveAnimate = appData.animate;
6332     if (clickType == Press) {
6333         /* Finish clickclick move */
6334         if (appData.animate || appData.highlightLastMove) {
6335             SetHighlights(fromX, fromY, toX, toY);
6336         } else {
6337             ClearHighlights();
6338         }
6339     } else {
6340         /* Finish drag move */
6341         if (appData.highlightLastMove) {
6342             SetHighlights(fromX, fromY, toX, toY);
6343         } else {
6344             ClearHighlights();
6345         }
6346         DragPieceEnd(xPix, yPix);
6347         /* Don't animate move and drag both */
6348         appData.animate = FALSE;
6349     }
6350
6351     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6352     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6353         ChessSquare piece = boards[currentMove][fromY][fromX];
6354         if(gameMode == EditPosition && piece != EmptySquare &&
6355            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6356             int n;
6357              
6358             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6359                 n = PieceToNumber(piece - (int)BlackPawn);
6360                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6361                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6362                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6363             } else
6364             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6365                 n = PieceToNumber(piece);
6366                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6367                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6368                 boards[currentMove][n][BOARD_WIDTH-2]++;
6369             }
6370             boards[currentMove][fromY][fromX] = EmptySquare;
6371         }
6372         ClearHighlights();
6373         fromX = fromY = -1;
6374         DrawPosition(TRUE, boards[currentMove]);
6375         return;
6376     }
6377
6378     // off-board moves should not be highlighted
6379     if(x < 0 || x < 0) ClearHighlights();
6380
6381     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6382         SetHighlights(fromX, fromY, toX, toY);
6383         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6384             // [HGM] super: promotion to captured piece selected from holdings
6385             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6386             promotionChoice = TRUE;
6387             // kludge follows to temporarily execute move on display, without promoting yet
6388             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6389             boards[currentMove][toY][toX] = p;
6390             DrawPosition(FALSE, boards[currentMove]);
6391             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6392             boards[currentMove][toY][toX] = q;
6393             DisplayMessage("Click in holdings to choose piece", "");
6394             return;
6395         }
6396         PromotionPopUp();
6397     } else {
6398         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6399         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6400         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6401         fromX = fromY = -1;
6402     }
6403     appData.animate = saveAnimate;
6404     if (appData.animate || appData.animateDragging) {
6405         /* Undo animation damage if needed */
6406         DrawPosition(FALSE, NULL);
6407     }
6408 }
6409
6410 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6411 {   // front-end-free part taken out of PieceMenuPopup
6412     int whichMenu; int xSqr, ySqr;
6413
6414     if(seekGraphUp) { // [HGM] seekgraph
6415         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6416         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6417         return -2;
6418     }
6419
6420     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6421          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6422         if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
6423         if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
6424         return -2;
6425     }
6426
6427     xSqr = EventToSquare(x, BOARD_WIDTH);
6428     ySqr = EventToSquare(y, BOARD_HEIGHT);
6429     if (action == Release) UnLoadPV(); // [HGM] pv
6430     if (action != Press) return -2; // return code to be ignored
6431     switch (gameMode) {
6432       case IcsExamining:
6433         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6434       case EditPosition:
6435         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6436         if (xSqr < 0 || ySqr < 0) return -1;\r
6437         whichMenu = 0; // edit-position menu
6438         break;
6439       case IcsObserving:
6440         if(!appData.icsEngineAnalyze) return -1;
6441       case IcsPlayingWhite:
6442       case IcsPlayingBlack:
6443         if(!appData.zippyPlay) goto noZip;
6444       case AnalyzeMode:
6445       case AnalyzeFile:
6446       case MachinePlaysWhite:
6447       case MachinePlaysBlack:
6448       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6449         if (!appData.dropMenu) {
6450           LoadPV(x, y);
6451           return 2; // flag front-end to grab mouse events
6452         }
6453         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6454            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6455       case EditGame:
6456       noZip:
6457         if (xSqr < 0 || ySqr < 0) return -1;
6458         if (!appData.dropMenu || appData.testLegality &&
6459             gameInfo.variant != VariantBughouse &&
6460             gameInfo.variant != VariantCrazyhouse) return -1;
6461         whichMenu = 1; // drop menu
6462         break;
6463       default:
6464         return -1;
6465     }
6466
6467     if (((*fromX = xSqr) < 0) ||
6468         ((*fromY = ySqr) < 0)) {
6469         *fromX = *fromY = -1;
6470         return -1;
6471     }
6472     if (flipView)
6473       *fromX = BOARD_WIDTH - 1 - *fromX;
6474     else
6475       *fromY = BOARD_HEIGHT - 1 - *fromY;
6476
6477     return whichMenu;
6478 }
6479
6480 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6481 {
6482 //    char * hint = lastHint;
6483     FrontEndProgramStats stats;
6484
6485     stats.which = cps == &first ? 0 : 1;
6486     stats.depth = cpstats->depth;
6487     stats.nodes = cpstats->nodes;
6488     stats.score = cpstats->score;
6489     stats.time = cpstats->time;
6490     stats.pv = cpstats->movelist;
6491     stats.hint = lastHint;
6492     stats.an_move_index = 0;
6493     stats.an_move_count = 0;
6494
6495     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6496         stats.hint = cpstats->move_name;
6497         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6498         stats.an_move_count = cpstats->nr_moves;
6499     }
6500
6501     if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6502
6503     SetProgramStats( &stats );
6504 }
6505
6506 int
6507 Adjudicate(ChessProgramState *cps)
6508 {       // [HGM] some adjudications useful with buggy engines
6509         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6510         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6511         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6512         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6513         int k, count = 0; static int bare = 1;
6514         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6515         Boolean canAdjudicate = !appData.icsActive;
6516
6517         // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6518         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6519             if( appData.testLegality )
6520             {   /* [HGM] Some more adjudications for obstinate engines */
6521                 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6522                     NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6523                     NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6524                 static int moveCount = 6;
6525                 ChessMove result;
6526                 char *reason = NULL;
6527
6528                 /* Count what is on board. */
6529                 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6530                 {   ChessSquare p = boards[forwardMostMove][i][j];
6531                     int m=i;
6532
6533                     switch((int) p)
6534                     {   /* count B,N,R and other of each side */
6535                         case WhiteKing:
6536                         case BlackKing:
6537                              NrK++; break; // [HGM] atomic: count Kings
6538                         case WhiteKnight:
6539                              NrWN++; break;
6540                         case WhiteBishop:
6541                         case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6542                              bishopsColor |= 1 << ((i^j)&1);
6543                              NrWB++; break;
6544                         case BlackKnight:
6545                              NrBN++; break;
6546                         case BlackBishop:
6547                         case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
6548                              bishopsColor |= 1 << ((i^j)&1);
6549                              NrBB++; break;
6550                         case WhiteRook:
6551                              NrWR++; break;
6552                         case BlackRook:
6553                              NrBR++; break;
6554                         case WhiteQueen:
6555                              NrWQ++; break;
6556                         case BlackQueen:
6557                              NrBQ++; break;
6558                         case EmptySquare: 
6559                              break;
6560                         case BlackPawn:
6561                              m = 7-i;
6562                         case WhitePawn:
6563                              PawnAdvance += m; NrPawns++;
6564                     }
6565                     NrPieces += (p != EmptySquare);
6566                     NrW += ((int)p < (int)BlackPawn);
6567                     if(gameInfo.variant == VariantXiangqi && 
6568                       (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6569                         NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6570                         NrW -= ((int)p < (int)BlackPawn);
6571                     }
6572                 }
6573
6574                 /* Some material-based adjudications that have to be made before stalemate test */
6575                 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6576                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6577                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6578                      if(canAdjudicate && appData.checkMates) {
6579                          if(engineOpponent)
6580                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6581                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6582                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, 
6583                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6584                          return 1;
6585                      }
6586                 }
6587
6588                 /* Bare King in Shatranj (loses) or Losers (wins) */
6589                 if( NrW == 1 || NrPieces - NrW == 1) {
6590                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6591                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6592                      if(canAdjudicate && appData.checkMates) {
6593                          if(engineOpponent)
6594                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6595                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6596                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6597                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6598                          return 1;
6599                      }
6600                   } else
6601                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6602                   {    /* bare King */
6603                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6604                         if(canAdjudicate && appData.checkMates) {
6605                             /* but only adjudicate if adjudication enabled */
6606                             if(engineOpponent)
6607                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6608                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6609                             GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
6610                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6611                             return 1;
6612                         }
6613                   }
6614                 } else bare = 1;
6615
6616
6617             // don't wait for engine to announce game end if we can judge ourselves
6618             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6619               case MT_CHECK:
6620                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6621                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6622                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6623                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6624                             checkCnt++;
6625                         if(checkCnt >= 2) {
6626                             reason = "Xboard adjudication: 3rd check";
6627                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6628                             break;
6629                         }
6630                     }
6631                 }
6632               case MT_NONE:
6633               default:
6634                 break;
6635               case MT_STALEMATE:
6636               case MT_STAINMATE:
6637                 reason = "Xboard adjudication: Stalemate";
6638                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6639                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6640                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6641                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6642                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6643                         boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6644                                                    ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6645                                                                         EP_CHECKMATE : EP_WINS);
6646                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6647                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6648                 }
6649                 break;
6650               case MT_CHECKMATE:
6651                 reason = "Xboard adjudication: Checkmate";
6652                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6653                 break;
6654             }
6655
6656                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6657                     case EP_STALEMATE:
6658                         result = GameIsDrawn; break;
6659                     case EP_CHECKMATE:
6660                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6661                     case EP_WINS:
6662                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6663                     default:
6664                         result = (ChessMove) 0;
6665                 }
6666                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6667                     if(engineOpponent)
6668                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6669                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6670                     GameEnds( result, reason, GE_XBOARD );
6671                     return 1;
6672                 }
6673
6674                 /* Next absolutely insufficient mating material. */
6675                 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
6676                                      gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6677                         (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6678                          NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6679                 {    /* KBK, KNK, KK of KBKB with like Bishops */
6680
6681                      /* always flag draws, for judging claims */
6682                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6683
6684                      if(canAdjudicate && appData.materialDraws) {
6685                          /* but only adjudicate them if adjudication enabled */
6686                          if(engineOpponent) {
6687                            SendToProgram("force\n", engineOpponent); // suppress reply
6688                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6689                          }
6690                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6691                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6692                          return 1;
6693                      }
6694                 }
6695
6696                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6697                 if(NrPieces == 4 && 
6698                    (   NrWR == 1 && NrBR == 1 /* KRKR */
6699                    || NrWQ==1 && NrBQ==1     /* KQKQ */
6700                    || NrWN==2 || NrBN==2     /* KNNK */
6701                    || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6702                   ) ) {
6703                      if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6704                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6705                           if(engineOpponent) {
6706                             SendToProgram("force\n", engineOpponent); // suppress reply
6707                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6708                           }
6709                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6710                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6711                           return 1;
6712                      }
6713                 } else moveCount = 6;
6714             }
6715         }
6716           
6717         if (appData.debugMode) { int i;
6718             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6719                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6720                     appData.drawRepeats);
6721             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6722               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6723             
6724         }
6725
6726         // Repetition draws and 50-move rule can be applied independently of legality testing
6727
6728                 /* Check for rep-draws */
6729                 count = 0;
6730                 for(k = forwardMostMove-2;
6731                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6732                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6733                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6734                     k-=2)
6735                 {   int rights=0;
6736                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6737                         /* compare castling rights */
6738                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6739                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6740                                 rights++; /* King lost rights, while rook still had them */
6741                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6742                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6743                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6744                                    rights++; /* but at least one rook lost them */
6745                         }
6746                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6747                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6748                                 rights++; 
6749                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6750                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6751                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6752                                    rights++;
6753                         }
6754                         if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6755                             && appData.drawRepeats > 1) {
6756                              /* adjudicate after user-specified nr of repeats */
6757                              if(engineOpponent) {
6758                                SendToProgram("force\n", engineOpponent); // suppress reply
6759                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6760                              }
6761                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6762                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
6763                                 // [HGM] xiangqi: check for forbidden perpetuals
6764                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6765                                 for(m=forwardMostMove; m>k; m-=2) {
6766                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6767                                         ourPerpetual = 0; // the current mover did not always check
6768                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6769                                         hisPerpetual = 0; // the opponent did not always check
6770                                 }
6771                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6772                                                                         ourPerpetual, hisPerpetual);
6773                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6774                                     GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6775                                            "Xboard adjudication: perpetual checking", GE_XBOARD );
6776                                     return 1;
6777                                 }
6778                                 if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
6779                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6780                                 // Now check for perpetual chases
6781                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6782                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6783                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6784                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6785                                         GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
6786                                                       "Xboard adjudication: perpetual chasing", GE_XBOARD );
6787                                         return 1;
6788                                     }
6789                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6790                                         break; // Abort repetition-checking loop.
6791                                 }
6792                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6793                              }
6794                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6795                              return 1;
6796                         }
6797                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6798                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6799                     }
6800                 }
6801
6802                 /* Now we test for 50-move draws. Determine ply count */
6803                 count = forwardMostMove;
6804                 /* look for last irreversble move */
6805                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6806                     count--;
6807                 /* if we hit starting position, add initial plies */
6808                 if( count == backwardMostMove )
6809                     count -= initialRulePlies;
6810                 count = forwardMostMove - count; 
6811                 if( count >= 100)
6812                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6813                          /* this is used to judge if draw claims are legal */
6814                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6815                          if(engineOpponent) {
6816                            SendToProgram("force\n", engineOpponent); // suppress reply
6817                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6818                          }
6819                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6820                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6821                          return 1;
6822                 }
6823
6824                 /* if draw offer is pending, treat it as a draw claim
6825                  * when draw condition present, to allow engines a way to
6826                  * claim draws before making their move to avoid a race
6827                  * condition occurring after their move
6828                  */
6829                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6830                          char *p = NULL;
6831                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6832                              p = "Draw claim: 50-move rule";
6833                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6834                              p = "Draw claim: 3-fold repetition";
6835                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6836                              p = "Draw claim: insufficient mating material";
6837                          if( p != NULL && canAdjudicate) {
6838                              if(engineOpponent) {
6839                                SendToProgram("force\n", engineOpponent); // suppress reply
6840                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6841                              }
6842                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6843                              GameEnds( GameIsDrawn, p, GE_XBOARD );
6844                              return 1;
6845                          }
6846                 }
6847
6848                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6849                     if(engineOpponent) {
6850                       SendToProgram("force\n", engineOpponent); // suppress reply
6851                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6852                     }
6853                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6854                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6855                     return 1;
6856                 }
6857         return 0;
6858 }
6859
6860 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6861 {   // [HGM] book: this routine intercepts moves to simulate book replies
6862     char *bookHit = NULL;
6863
6864     //first determine if the incoming move brings opponent into his book
6865     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6866         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6867     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6868     if(bookHit != NULL && !cps->bookSuspend) {
6869         // make sure opponent is not going to reply after receiving move to book position
6870         SendToProgram("force\n", cps);
6871         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6872     }
6873     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6874     // now arrange restart after book miss
6875     if(bookHit) {
6876         // after a book hit we never send 'go', and the code after the call to this routine
6877         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6878         char buf[MSG_SIZ];
6879         if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6880         sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6881         SendToProgram(buf, cps);
6882         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6883     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6884         SendToProgram("go\n", cps);
6885         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6886     } else { // 'go' might be sent based on 'firstMove' after this routine returns
6887         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6888             SendToProgram("go\n", cps); 
6889         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6890     }
6891     return bookHit; // notify caller of hit, so it can take action to send move to opponent
6892 }
6893
6894 char *savedMessage;
6895 ChessProgramState *savedState;
6896 void DeferredBookMove(void)
6897 {
6898         if(savedState->lastPing != savedState->lastPong)
6899                     ScheduleDelayedEvent(DeferredBookMove, 10);
6900         else
6901         HandleMachineMove(savedMessage, savedState);
6902 }
6903
6904 void
6905 HandleMachineMove(message, cps)
6906      char *message;
6907      ChessProgramState *cps;
6908 {
6909     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6910     char realname[MSG_SIZ];
6911     int fromX, fromY, toX, toY;
6912     ChessMove moveType;
6913     char promoChar;
6914     char *p;
6915     int machineWhite;
6916     char *bookHit;
6917
6918     cps->userError = 0;
6919
6920 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6921     /*
6922      * Kludge to ignore BEL characters
6923      */
6924     while (*message == '\007') message++;
6925
6926     /*
6927      * [HGM] engine debug message: ignore lines starting with '#' character
6928      */
6929     if(cps->debug && *message == '#') return;
6930
6931     /*
6932      * Look for book output
6933      */
6934     if (cps == &first && bookRequested) {
6935         if (message[0] == '\t' || message[0] == ' ') {
6936             /* Part of the book output is here; append it */
6937             strcat(bookOutput, message);
6938             strcat(bookOutput, "  \n");
6939             return;
6940         } else if (bookOutput[0] != NULLCHAR) {
6941             /* All of book output has arrived; display it */
6942             char *p = bookOutput;
6943             while (*p != NULLCHAR) {
6944                 if (*p == '\t') *p = ' ';
6945                 p++;
6946             }
6947             DisplayInformation(bookOutput);
6948             bookRequested = FALSE;
6949             /* Fall through to parse the current output */
6950         }
6951     }
6952
6953     /*
6954      * Look for machine move.
6955      */
6956     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6957         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) 
6958     {
6959         /* This method is only useful on engines that support ping */
6960         if (cps->lastPing != cps->lastPong) {
6961           if (gameMode == BeginningOfGame) {
6962             /* Extra move from before last new; ignore */
6963             if (appData.debugMode) {
6964                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6965             }
6966           } else {
6967             if (appData.debugMode) {
6968                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6969                         cps->which, gameMode);
6970             }
6971
6972             SendToProgram("undo\n", cps);
6973           }
6974           return;
6975         }
6976
6977         switch (gameMode) {
6978           case BeginningOfGame:
6979             /* Extra move from before last reset; ignore */
6980             if (appData.debugMode) {
6981                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6982             }
6983             return;
6984
6985           case EndOfGame:
6986           case IcsIdle:
6987           default:
6988             /* Extra move after we tried to stop.  The mode test is
6989                not a reliable way of detecting this problem, but it's
6990                the best we can do on engines that don't support ping.
6991             */
6992             if (appData.debugMode) {
6993                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6994                         cps->which, gameMode);
6995             }
6996             SendToProgram("undo\n", cps);
6997             return;
6998
6999           case MachinePlaysWhite:
7000           case IcsPlayingWhite:
7001             machineWhite = TRUE;
7002             break;
7003
7004           case MachinePlaysBlack:
7005           case IcsPlayingBlack:
7006             machineWhite = FALSE;
7007             break;
7008
7009           case TwoMachinesPlay:
7010             machineWhite = (cps->twoMachinesColor[0] == 'w');
7011             break;
7012         }
7013         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7014             if (appData.debugMode) {
7015                 fprintf(debugFP,
7016                         "Ignoring move out of turn by %s, gameMode %d"
7017                         ", forwardMost %d\n",
7018                         cps->which, gameMode, forwardMostMove);
7019             }
7020             return;
7021         }
7022
7023     if (appData.debugMode) { int f = forwardMostMove;
7024         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7025                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7026                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7027     }
7028         if(cps->alphaRank) AlphaRank(machineMove, 4);
7029         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7030                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7031             /* Machine move could not be parsed; ignore it. */
7032             sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
7033                     machineMove, cps->which);
7034             DisplayError(buf1, 0);
7035             sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7036                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7037             if (gameMode == TwoMachinesPlay) {
7038               GameEnds(machineWhite ? BlackWins : WhiteWins,
7039                        buf1, GE_XBOARD);
7040             }
7041             return;
7042         }
7043
7044         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7045         /* So we have to redo legality test with true e.p. status here,  */
7046         /* to make sure an illegal e.p. capture does not slip through,   */
7047         /* to cause a forfeit on a justified illegal-move complaint      */
7048         /* of the opponent.                                              */
7049         if( gameMode==TwoMachinesPlay && appData.testLegality
7050             && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
7051                                                               ) {
7052            ChessMove moveType;
7053            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7054                              fromY, fromX, toY, toX, promoChar);
7055             if (appData.debugMode) {
7056                 int i;
7057                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7058                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7059                 fprintf(debugFP, "castling rights\n");
7060             }
7061             if(moveType == IllegalMove) {
7062                 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7063                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7064                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7065                            buf1, GE_XBOARD);
7066                 return;
7067            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7068            /* [HGM] Kludge to handle engines that send FRC-style castling
7069               when they shouldn't (like TSCP-Gothic) */
7070            switch(moveType) {
7071              case WhiteASideCastleFR:
7072              case BlackASideCastleFR:
7073                toX+=2;
7074                currentMoveString[2]++;
7075                break;
7076              case WhiteHSideCastleFR:
7077              case BlackHSideCastleFR:
7078                toX--;
7079                currentMoveString[2]--;
7080                break;
7081              default: ; // nothing to do, but suppresses warning of pedantic compilers
7082            }
7083         }
7084         hintRequested = FALSE;
7085         lastHint[0] = NULLCHAR;
7086         bookRequested = FALSE;
7087         /* Program may be pondering now */
7088         cps->maybeThinking = TRUE;
7089         if (cps->sendTime == 2) cps->sendTime = 1;
7090         if (cps->offeredDraw) cps->offeredDraw--;
7091
7092         /* currentMoveString is set as a side-effect of ParseOneMove */
7093         strcpy(machineMove, currentMoveString);
7094         strcat(machineMove, "\n");
7095         strcpy(moveList[forwardMostMove], machineMove);
7096
7097         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7098
7099         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7100         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7101             int count = 0;
7102
7103             while( count < adjudicateLossPlies ) {
7104                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7105
7106                 if( count & 1 ) {
7107                     score = -score; /* Flip score for winning side */
7108                 }
7109
7110                 if( score > adjudicateLossThreshold ) {
7111                     break;
7112                 }
7113
7114                 count++;
7115             }
7116
7117             if( count >= adjudicateLossPlies ) {
7118                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7119
7120                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
7121                     "Xboard adjudication", 
7122                     GE_XBOARD );
7123
7124                 return;
7125             }
7126         }
7127
7128         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7129
7130 #if ZIPPY
7131         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7132             first.initDone) {
7133           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7134                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7135                 SendToICS("draw ");
7136                 SendMoveToICS(moveType, fromX, fromY, toX, toY);
7137           }
7138           SendMoveToICS(moveType, fromX, fromY, toX, toY);
7139           ics_user_moved = 1;
7140           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7141                 char buf[3*MSG_SIZ];
7142
7143                 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7144                         programStats.score / 100.,
7145                         programStats.depth,
7146                         programStats.time / 100.,
7147                         (unsigned int)programStats.nodes,
7148                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7149                         programStats.movelist);
7150                 SendToICS(buf);
7151 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7152           }
7153         }
7154 #endif
7155
7156         /* [AS] Save move info and clear stats for next move */
7157         pvInfoList[ forwardMostMove-1 ].score = programStats.score;
7158         pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
7159         pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7160         ClearProgramStats();
7161         thinkOutput[0] = NULLCHAR;
7162         hiddenThinkOutputState = 0;
7163
7164         bookHit = NULL;
7165         if (gameMode == TwoMachinesPlay) {
7166             /* [HGM] relaying draw offers moved to after reception of move */
7167             /* and interpreting offer as claim if it brings draw condition */
7168             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7169                 SendToProgram("draw\n", cps->other);
7170             }
7171             if (cps->other->sendTime) {
7172                 SendTimeRemaining(cps->other,
7173                                   cps->other->twoMachinesColor[0] == 'w');
7174             }
7175             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7176             if (firstMove && !bookHit) {
7177                 firstMove = FALSE;
7178                 if (cps->other->useColors) {
7179                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7180                 }
7181                 SendToProgram("go\n", cps->other);
7182             }
7183             cps->other->maybeThinking = TRUE;
7184         }
7185
7186         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7187         
7188         if (!pausing && appData.ringBellAfterMoves) {
7189             RingBell();
7190         }
7191
7192         /* 
7193          * Reenable menu items that were disabled while
7194          * machine was thinking
7195          */
7196         if (gameMode != TwoMachinesPlay)
7197             SetUserThinkingEnables();
7198
7199         // [HGM] book: after book hit opponent has received move and is now in force mode
7200         // force the book reply into it, and then fake that it outputted this move by jumping
7201         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7202         if(bookHit) {
7203                 static char bookMove[MSG_SIZ]; // a bit generous?
7204
7205                 strcpy(bookMove, "move ");
7206                 strcat(bookMove, bookHit);
7207                 message = bookMove;
7208                 cps = cps->other;
7209                 programStats.nodes = programStats.depth = programStats.time = 
7210                 programStats.score = programStats.got_only_move = 0;
7211                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7212
7213                 if(cps->lastPing != cps->lastPong) {
7214                     savedMessage = message; // args for deferred call
7215                     savedState = cps;
7216                     ScheduleDelayedEvent(DeferredBookMove, 10);
7217                     return;
7218                 }
7219                 goto FakeBookMove;
7220         }
7221
7222         return;
7223     }
7224
7225     /* Set special modes for chess engines.  Later something general
7226      *  could be added here; for now there is just one kludge feature,
7227      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7228      *  when "xboard" is given as an interactive command.
7229      */
7230     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7231         cps->useSigint = FALSE;
7232         cps->useSigterm = FALSE;
7233     }
7234     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7235       ParseFeatures(message+8, cps);
7236       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7237     }
7238
7239     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7240      * want this, I was asked to put it in, and obliged.
7241      */
7242     if (!strncmp(message, "setboard ", 9)) {
7243         Board initial_position;
7244
7245         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7246
7247         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7248             DisplayError(_("Bad FEN received from engine"), 0);
7249             return ;
7250         } else {
7251            Reset(TRUE, FALSE);
7252            CopyBoard(boards[0], initial_position);
7253            initialRulePlies = FENrulePlies;
7254            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7255            else gameMode = MachinePlaysBlack;                 
7256            DrawPosition(FALSE, boards[currentMove]);
7257         }
7258         return;
7259     }
7260
7261     /*
7262      * Look for communication commands
7263      */
7264     if (!strncmp(message, "telluser ", 9)) {
7265         DisplayNote(message + 9);
7266         return;
7267     }
7268     if (!strncmp(message, "tellusererror ", 14)) {
7269         cps->userError = 1;
7270         DisplayError(message + 14, 0);
7271         return;
7272     }
7273     if (!strncmp(message, "tellopponent ", 13)) {
7274       if (appData.icsActive) {
7275         if (loggedOn) {
7276           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7277           SendToICS(buf1);
7278         }
7279       } else {
7280         DisplayNote(message + 13);
7281       }
7282       return;
7283     }
7284     if (!strncmp(message, "tellothers ", 11)) {
7285       if (appData.icsActive) {
7286         if (loggedOn) {
7287           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7288           SendToICS(buf1);
7289         }
7290       }
7291       return;
7292     }
7293     if (!strncmp(message, "tellall ", 8)) {
7294       if (appData.icsActive) {
7295         if (loggedOn) {
7296           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7297           SendToICS(buf1);
7298         }
7299       } else {
7300         DisplayNote(message + 8);
7301       }
7302       return;
7303     }
7304     if (strncmp(message, "warning", 7) == 0) {
7305         /* Undocumented feature, use tellusererror in new code */
7306         DisplayError(message, 0);
7307         return;
7308     }
7309     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7310         strcpy(realname, cps->tidy);
7311         strcat(realname, " query");
7312         AskQuestion(realname, buf2, buf1, cps->pr);
7313         return;
7314     }
7315     /* Commands from the engine directly to ICS.  We don't allow these to be 
7316      *  sent until we are logged on. Crafty kibitzes have been known to 
7317      *  interfere with the login process.
7318      */
7319     if (loggedOn) {
7320         if (!strncmp(message, "tellics ", 8)) {
7321             SendToICS(message + 8);
7322             SendToICS("\n");
7323             return;
7324         }
7325         if (!strncmp(message, "tellicsnoalias ", 15)) {
7326             SendToICS(ics_prefix);
7327             SendToICS(message + 15);
7328             SendToICS("\n");
7329             return;
7330         }
7331         /* The following are for backward compatibility only */
7332         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7333             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7334             SendToICS(ics_prefix);
7335             SendToICS(message);
7336             SendToICS("\n");
7337             return;
7338         }
7339     }
7340     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7341         return;
7342     }
7343     /*
7344      * If the move is illegal, cancel it and redraw the board.
7345      * Also deal with other error cases.  Matching is rather loose
7346      * here to accommodate engines written before the spec.
7347      */
7348     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7349         strncmp(message, "Error", 5) == 0) {
7350         if (StrStr(message, "name") || 
7351             StrStr(message, "rating") || StrStr(message, "?") ||
7352             StrStr(message, "result") || StrStr(message, "board") ||
7353             StrStr(message, "bk") || StrStr(message, "computer") ||
7354             StrStr(message, "variant") || StrStr(message, "hint") ||
7355             StrStr(message, "random") || StrStr(message, "depth") ||
7356             StrStr(message, "accepted")) {
7357             return;
7358         }
7359         if (StrStr(message, "protover")) {
7360           /* Program is responding to input, so it's apparently done
7361              initializing, and this error message indicates it is
7362              protocol version 1.  So we don't need to wait any longer
7363              for it to initialize and send feature commands. */
7364           FeatureDone(cps, 1);
7365           cps->protocolVersion = 1;
7366           return;
7367         }
7368         cps->maybeThinking = FALSE;
7369
7370         if (StrStr(message, "draw")) {
7371             /* Program doesn't have "draw" command */
7372             cps->sendDrawOffers = 0;
7373             return;
7374         }
7375         if (cps->sendTime != 1 &&
7376             (StrStr(message, "time") || StrStr(message, "otim"))) {
7377           /* Program apparently doesn't have "time" or "otim" command */
7378           cps->sendTime = 0;
7379           return;
7380         }
7381         if (StrStr(message, "analyze")) {
7382             cps->analysisSupport = FALSE;
7383             cps->analyzing = FALSE;
7384             Reset(FALSE, TRUE);
7385             sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7386             DisplayError(buf2, 0);
7387             return;
7388         }
7389         if (StrStr(message, "(no matching move)st")) {
7390           /* Special kludge for GNU Chess 4 only */
7391           cps->stKludge = TRUE;
7392           SendTimeControl(cps, movesPerSession, timeControl,
7393                           timeIncrement, appData.searchDepth,
7394                           searchTime);
7395           return;
7396         }
7397         if (StrStr(message, "(no matching move)sd")) {
7398           /* Special kludge for GNU Chess 4 only */
7399           cps->sdKludge = TRUE;
7400           SendTimeControl(cps, movesPerSession, timeControl,
7401                           timeIncrement, appData.searchDepth,
7402                           searchTime);
7403           return;
7404         }
7405         if (!StrStr(message, "llegal")) {
7406             return;
7407         }
7408         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7409             gameMode == IcsIdle) return;
7410         if (forwardMostMove <= backwardMostMove) return;
7411         if (pausing) PauseEvent();
7412       if(appData.forceIllegal) {
7413             // [HGM] illegal: machine refused move; force position after move into it
7414           SendToProgram("force\n", cps);
7415           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7416                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7417                 // when black is to move, while there might be nothing on a2 or black
7418                 // might already have the move. So send the board as if white has the move.
7419                 // But first we must change the stm of the engine, as it refused the last move
7420                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7421                 if(WhiteOnMove(forwardMostMove)) {
7422                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7423                     SendBoard(cps, forwardMostMove); // kludgeless board
7424                 } else {
7425                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7426                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7427                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7428                 }
7429           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7430             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7431                  gameMode == TwoMachinesPlay)
7432               SendToProgram("go\n", cps);
7433             return;
7434       } else
7435         if (gameMode == PlayFromGameFile) {
7436             /* Stop reading this game file */
7437             gameMode = EditGame;
7438             ModeHighlight();
7439         }
7440         currentMove = forwardMostMove-1;
7441         DisplayMove(currentMove-1); /* before DisplayMoveError */
7442         SwitchClocks(forwardMostMove-1); // [HGM] race
7443         DisplayBothClocks();
7444         sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7445                 parseList[currentMove], cps->which);
7446         DisplayMoveError(buf1);
7447         DrawPosition(FALSE, boards[currentMove]);
7448
7449         /* [HGM] illegal-move claim should forfeit game when Xboard */
7450         /* only passes fully legal moves                            */
7451         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7452             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7453                                 "False illegal-move claim", GE_XBOARD );
7454         }
7455         return;
7456     }
7457     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7458         /* Program has a broken "time" command that
7459            outputs a string not ending in newline.
7460            Don't use it. */
7461         cps->sendTime = 0;
7462     }
7463     
7464     /*
7465      * If chess program startup fails, exit with an error message.
7466      * Attempts to recover here are futile.
7467      */
7468     if ((StrStr(message, "unknown host") != NULL)
7469         || (StrStr(message, "No remote directory") != NULL)
7470         || (StrStr(message, "not found") != NULL)
7471         || (StrStr(message, "No such file") != NULL)
7472         || (StrStr(message, "can't alloc") != NULL)
7473         || (StrStr(message, "Permission denied") != NULL)) {
7474
7475         cps->maybeThinking = FALSE;
7476         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7477                 cps->which, cps->program, cps->host, message);
7478         RemoveInputSource(cps->isr);
7479         DisplayFatalError(buf1, 0, 1);
7480         return;
7481     }
7482     
7483     /* 
7484      * Look for hint output
7485      */
7486     if (sscanf(message, "Hint: %s", buf1) == 1) {
7487         if (cps == &first && hintRequested) {
7488             hintRequested = FALSE;
7489             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7490                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7491                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7492                                     PosFlags(forwardMostMove),
7493                                     fromY, fromX, toY, toX, promoChar, buf1);
7494                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7495                 DisplayInformation(buf2);
7496             } else {
7497                 /* Hint move could not be parsed!? */
7498               snprintf(buf2, sizeof(buf2),
7499                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7500                         buf1, cps->which);
7501                 DisplayError(buf2, 0);
7502             }
7503         } else {
7504             strcpy(lastHint, buf1);
7505         }
7506         return;
7507     }
7508
7509     /*
7510      * Ignore other messages if game is not in progress
7511      */
7512     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7513         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7514
7515     /*
7516      * look for win, lose, draw, or draw offer
7517      */
7518     if (strncmp(message, "1-0", 3) == 0) {
7519         char *p, *q, *r = "";
7520         p = strchr(message, '{');
7521         if (p) {
7522             q = strchr(p, '}');
7523             if (q) {
7524                 *q = NULLCHAR;
7525                 r = p + 1;
7526             }
7527         }
7528         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7529         return;
7530     } else if (strncmp(message, "0-1", 3) == 0) {
7531         char *p, *q, *r = "";
7532         p = strchr(message, '{');
7533         if (p) {
7534             q = strchr(p, '}');
7535             if (q) {
7536                 *q = NULLCHAR;
7537                 r = p + 1;
7538             }
7539         }
7540         /* Kludge for Arasan 4.1 bug */
7541         if (strcmp(r, "Black resigns") == 0) {
7542             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7543             return;
7544         }
7545         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7546         return;
7547     } else if (strncmp(message, "1/2", 3) == 0) {
7548         char *p, *q, *r = "";
7549         p = strchr(message, '{');
7550         if (p) {
7551             q = strchr(p, '}');
7552             if (q) {
7553                 *q = NULLCHAR;
7554                 r = p + 1;
7555             }
7556         }
7557             
7558         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7559         return;
7560
7561     } else if (strncmp(message, "White resign", 12) == 0) {
7562         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7563         return;
7564     } else if (strncmp(message, "Black resign", 12) == 0) {
7565         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7566         return;
7567     } else if (strncmp(message, "White matches", 13) == 0 ||
7568                strncmp(message, "Black matches", 13) == 0   ) {
7569         /* [HGM] ignore GNUShogi noises */
7570         return;
7571     } else if (strncmp(message, "White", 5) == 0 &&
7572                message[5] != '(' &&
7573                StrStr(message, "Black") == NULL) {
7574         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7575         return;
7576     } else if (strncmp(message, "Black", 5) == 0 &&
7577                message[5] != '(') {
7578         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7579         return;
7580     } else if (strcmp(message, "resign") == 0 ||
7581                strcmp(message, "computer resigns") == 0) {
7582         switch (gameMode) {
7583           case MachinePlaysBlack:
7584           case IcsPlayingBlack:
7585             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7586             break;
7587           case MachinePlaysWhite:
7588           case IcsPlayingWhite:
7589             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7590             break;
7591           case TwoMachinesPlay:
7592             if (cps->twoMachinesColor[0] == 'w')
7593               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7594             else
7595               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7596             break;
7597           default:
7598             /* can't happen */
7599             break;
7600         }
7601         return;
7602     } else if (strncmp(message, "opponent mates", 14) == 0) {
7603         switch (gameMode) {
7604           case MachinePlaysBlack:
7605           case IcsPlayingBlack:
7606             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7607             break;
7608           case MachinePlaysWhite:
7609           case IcsPlayingWhite:
7610             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7611             break;
7612           case TwoMachinesPlay:
7613             if (cps->twoMachinesColor[0] == 'w')
7614               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7615             else
7616               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7617             break;
7618           default:
7619             /* can't happen */
7620             break;
7621         }
7622         return;
7623     } else if (strncmp(message, "computer mates", 14) == 0) {
7624         switch (gameMode) {
7625           case MachinePlaysBlack:
7626           case IcsPlayingBlack:
7627             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7628             break;
7629           case MachinePlaysWhite:
7630           case IcsPlayingWhite:
7631             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7632             break;
7633           case TwoMachinesPlay:
7634             if (cps->twoMachinesColor[0] == 'w')
7635               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7636             else
7637               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7638             break;
7639           default:
7640             /* can't happen */
7641             break;
7642         }
7643         return;
7644     } else if (strncmp(message, "checkmate", 9) == 0) {
7645         if (WhiteOnMove(forwardMostMove)) {
7646             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7647         } else {
7648             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7649         }
7650         return;
7651     } else if (strstr(message, "Draw") != NULL ||
7652                strstr(message, "game is a draw") != NULL) {
7653         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7654         return;
7655     } else if (strstr(message, "offer") != NULL &&
7656                strstr(message, "draw") != NULL) {
7657 #if ZIPPY
7658         if (appData.zippyPlay && first.initDone) {
7659             /* Relay offer to ICS */
7660             SendToICS(ics_prefix);
7661             SendToICS("draw\n");
7662         }
7663 #endif
7664         cps->offeredDraw = 2; /* valid until this engine moves twice */
7665         if (gameMode == TwoMachinesPlay) {
7666             if (cps->other->offeredDraw) {
7667                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7668             /* [HGM] in two-machine mode we delay relaying draw offer      */
7669             /* until after we also have move, to see if it is really claim */
7670             }
7671         } else if (gameMode == MachinePlaysWhite ||
7672                    gameMode == MachinePlaysBlack) {
7673           if (userOfferedDraw) {
7674             DisplayInformation(_("Machine accepts your draw offer"));
7675             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7676           } else {
7677             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7678           }
7679         }
7680     }
7681
7682     
7683     /*
7684      * Look for thinking output
7685      */
7686     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7687           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7688                                 ) {
7689         int plylev, mvleft, mvtot, curscore, time;
7690         char mvname[MOVE_LEN];
7691         u64 nodes; // [DM]
7692         char plyext;
7693         int ignore = FALSE;
7694         int prefixHint = FALSE;
7695         mvname[0] = NULLCHAR;
7696
7697         switch (gameMode) {
7698           case MachinePlaysBlack:
7699           case IcsPlayingBlack:
7700             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7701             break;
7702           case MachinePlaysWhite:
7703           case IcsPlayingWhite:
7704             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7705             break;
7706           case AnalyzeMode:
7707           case AnalyzeFile:
7708             break;
7709           case IcsObserving: /* [DM] icsEngineAnalyze */
7710             if (!appData.icsEngineAnalyze) ignore = TRUE;
7711             break;
7712           case TwoMachinesPlay:
7713             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7714                 ignore = TRUE;
7715             }
7716             break;
7717           default:
7718             ignore = TRUE;
7719             break;
7720         }
7721
7722         if (!ignore) {
7723             buf1[0] = NULLCHAR;
7724             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7725                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7726
7727                 if (plyext != ' ' && plyext != '\t') {
7728                     time *= 100;
7729                 }
7730
7731                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7732                 if( cps->scoreIsAbsolute && 
7733                     ( gameMode == MachinePlaysBlack ||
7734                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7735                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7736                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7737                      !WhiteOnMove(currentMove)
7738                     ) )
7739                 {
7740                     curscore = -curscore;
7741                 }
7742
7743
7744                 programStats.depth = plylev;
7745                 programStats.nodes = nodes;
7746                 programStats.time = time;
7747                 programStats.score = curscore;
7748                 programStats.got_only_move = 0;
7749
7750                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7751                         int ticklen;
7752
7753                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7754                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7755                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7756                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w')) 
7757                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7758                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7759                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) 
7760                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7761                 }
7762
7763                 /* Buffer overflow protection */
7764                 if (buf1[0] != NULLCHAR) {
7765                     if (strlen(buf1) >= sizeof(programStats.movelist)
7766                         && appData.debugMode) {
7767                         fprintf(debugFP,
7768                                 "PV is too long; using the first %u bytes.\n",
7769                                 (unsigned) sizeof(programStats.movelist) - 1);
7770                     }
7771
7772                     safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7773                 } else {
7774                     sprintf(programStats.movelist, " no PV\n");
7775                 }
7776
7777                 if (programStats.seen_stat) {
7778                     programStats.ok_to_send = 1;
7779                 }
7780
7781                 if (strchr(programStats.movelist, '(') != NULL) {
7782                     programStats.line_is_book = 1;
7783                     programStats.nr_moves = 0;
7784                     programStats.moves_left = 0;
7785                 } else {
7786                     programStats.line_is_book = 0;
7787                 }
7788
7789                 SendProgramStatsToFrontend( cps, &programStats );
7790
7791                 /* 
7792                     [AS] Protect the thinkOutput buffer from overflow... this
7793                     is only useful if buf1 hasn't overflowed first!
7794                 */
7795                 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7796                         plylev, 
7797                         (gameMode == TwoMachinesPlay ?
7798                          ToUpper(cps->twoMachinesColor[0]) : ' '),
7799                         ((double) curscore) / 100.0,
7800                         prefixHint ? lastHint : "",
7801                         prefixHint ? " " : "" );
7802
7803                 if( buf1[0] != NULLCHAR ) {
7804                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7805
7806                     if( strlen(buf1) > max_len ) {
7807                         if( appData.debugMode) {
7808                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7809                         }
7810                         buf1[max_len+1] = '\0';
7811                     }
7812
7813                     strcat( thinkOutput, buf1 );
7814                 }
7815
7816                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7817                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7818                     DisplayMove(currentMove - 1);
7819                 }
7820                 return;
7821
7822             } else if ((p=StrStr(message, "(only move)")) != NULL) {
7823                 /* crafty (9.25+) says "(only move) <move>"
7824                  * if there is only 1 legal move
7825                  */
7826                 sscanf(p, "(only move) %s", buf1);
7827                 sprintf(thinkOutput, "%s (only move)", buf1);
7828                 sprintf(programStats.movelist, "%s (only move)", buf1);
7829                 programStats.depth = 1;
7830                 programStats.nr_moves = 1;
7831                 programStats.moves_left = 1;
7832                 programStats.nodes = 1;
7833                 programStats.time = 1;
7834                 programStats.got_only_move = 1;
7835
7836                 /* Not really, but we also use this member to
7837                    mean "line isn't going to change" (Crafty
7838                    isn't searching, so stats won't change) */
7839                 programStats.line_is_book = 1;
7840
7841                 SendProgramStatsToFrontend( cps, &programStats );
7842                 
7843                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode || 
7844                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7845                     DisplayMove(currentMove - 1);
7846                 }
7847                 return;
7848             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7849                               &time, &nodes, &plylev, &mvleft,
7850                               &mvtot, mvname) >= 5) {
7851                 /* The stat01: line is from Crafty (9.29+) in response
7852                    to the "." command */
7853                 programStats.seen_stat = 1;
7854                 cps->maybeThinking = TRUE;
7855
7856                 if (programStats.got_only_move || !appData.periodicUpdates)
7857                   return;
7858
7859                 programStats.depth = plylev;
7860                 programStats.time = time;
7861                 programStats.nodes = nodes;
7862                 programStats.moves_left = mvleft;
7863                 programStats.nr_moves = mvtot;
7864                 strcpy(programStats.move_name, mvname);
7865                 programStats.ok_to_send = 1;
7866                 programStats.movelist[0] = '\0';
7867
7868                 SendProgramStatsToFrontend( cps, &programStats );
7869
7870                 return;
7871
7872             } else if (strncmp(message,"++",2) == 0) {
7873                 /* Crafty 9.29+ outputs this */
7874                 programStats.got_fail = 2;
7875                 return;
7876
7877             } else if (strncmp(message,"--",2) == 0) {
7878                 /* Crafty 9.29+ outputs this */
7879                 programStats.got_fail = 1;
7880                 return;
7881
7882             } else if (thinkOutput[0] != NULLCHAR &&
7883                        strncmp(message, "    ", 4) == 0) {
7884                 unsigned message_len;
7885
7886                 p = message;
7887                 while (*p && *p == ' ') p++;
7888
7889                 message_len = strlen( p );
7890
7891                 /* [AS] Avoid buffer overflow */
7892                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7893                     strcat(thinkOutput, " ");
7894                     strcat(thinkOutput, p);
7895                 }
7896
7897                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7898                     strcat(programStats.movelist, " ");
7899                     strcat(programStats.movelist, p);
7900                 }
7901
7902                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7903                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7904                     DisplayMove(currentMove - 1);
7905                 }
7906                 return;
7907             }
7908         }
7909         else {
7910             buf1[0] = NULLCHAR;
7911
7912             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7913                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) 
7914             {
7915                 ChessProgramStats cpstats;
7916
7917                 if (plyext != ' ' && plyext != '\t') {
7918                     time *= 100;
7919                 }
7920
7921                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7922                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7923                     curscore = -curscore;
7924                 }
7925
7926                 cpstats.depth = plylev;
7927                 cpstats.nodes = nodes;
7928                 cpstats.time = time;
7929                 cpstats.score = curscore;
7930                 cpstats.got_only_move = 0;
7931                 cpstats.movelist[0] = '\0';
7932
7933                 if (buf1[0] != NULLCHAR) {
7934                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7935                 }
7936
7937                 cpstats.ok_to_send = 0;
7938                 cpstats.line_is_book = 0;
7939                 cpstats.nr_moves = 0;
7940                 cpstats.moves_left = 0;
7941
7942                 SendProgramStatsToFrontend( cps, &cpstats );
7943             }
7944         }
7945     }
7946 }
7947
7948
7949 /* Parse a game score from the character string "game", and
7950    record it as the history of the current game.  The game
7951    score is NOT assumed to start from the standard position. 
7952    The display is not updated in any way.
7953    */
7954 void
7955 ParseGameHistory(game)
7956      char *game;
7957 {
7958     ChessMove moveType;
7959     int fromX, fromY, toX, toY, boardIndex;
7960     char promoChar;
7961     char *p, *q;
7962     char buf[MSG_SIZ];
7963
7964     if (appData.debugMode)
7965       fprintf(debugFP, "Parsing game history: %s\n", game);
7966
7967     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7968     gameInfo.site = StrSave(appData.icsHost);
7969     gameInfo.date = PGNDate();
7970     gameInfo.round = StrSave("-");
7971
7972     /* Parse out names of players */
7973     while (*game == ' ') game++;
7974     p = buf;
7975     while (*game != ' ') *p++ = *game++;
7976     *p = NULLCHAR;
7977     gameInfo.white = StrSave(buf);
7978     while (*game == ' ') game++;
7979     p = buf;
7980     while (*game != ' ' && *game != '\n') *p++ = *game++;
7981     *p = NULLCHAR;
7982     gameInfo.black = StrSave(buf);
7983
7984     /* Parse moves */
7985     boardIndex = blackPlaysFirst ? 1 : 0;
7986     yynewstr(game);
7987     for (;;) {
7988         yyboardindex = boardIndex;
7989         moveType = (ChessMove) yylex();
7990         switch (moveType) {
7991           case IllegalMove:             /* maybe suicide chess, etc. */
7992   if (appData.debugMode) {
7993     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7994     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7995     setbuf(debugFP, NULL);
7996   }
7997           case WhitePromotionChancellor:
7998           case BlackPromotionChancellor:
7999           case WhitePromotionArchbishop:
8000           case BlackPromotionArchbishop:
8001           case WhitePromotionQueen:
8002           case BlackPromotionQueen:
8003           case WhitePromotionRook:
8004           case BlackPromotionRook:
8005           case WhitePromotionBishop:
8006           case BlackPromotionBishop:
8007           case WhitePromotionKnight:
8008           case BlackPromotionKnight:
8009           case WhitePromotionKing:
8010           case BlackPromotionKing:
8011           case NormalMove:
8012           case WhiteCapturesEnPassant:
8013           case BlackCapturesEnPassant:
8014           case WhiteKingSideCastle:
8015           case WhiteQueenSideCastle:
8016           case BlackKingSideCastle:
8017           case BlackQueenSideCastle:
8018           case WhiteKingSideCastleWild:
8019           case WhiteQueenSideCastleWild:
8020           case BlackKingSideCastleWild:
8021           case BlackQueenSideCastleWild:
8022           /* PUSH Fabien */
8023           case WhiteHSideCastleFR:
8024           case WhiteASideCastleFR:
8025           case BlackHSideCastleFR:
8026           case BlackASideCastleFR:
8027           /* POP Fabien */
8028             fromX = currentMoveString[0] - AAA;
8029             fromY = currentMoveString[1] - ONE;
8030             toX = currentMoveString[2] - AAA;
8031             toY = currentMoveString[3] - ONE;
8032             promoChar = currentMoveString[4];
8033             break;
8034           case WhiteDrop:
8035           case BlackDrop:
8036             fromX = moveType == WhiteDrop ?
8037               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8038             (int) CharToPiece(ToLower(currentMoveString[0]));
8039             fromY = DROP_RANK;
8040             toX = currentMoveString[2] - AAA;
8041             toY = currentMoveString[3] - ONE;
8042             promoChar = NULLCHAR;
8043             break;
8044           case AmbiguousMove:
8045             /* bug? */
8046             sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8047   if (appData.debugMode) {
8048     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8049     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8050     setbuf(debugFP, NULL);
8051   }
8052             DisplayError(buf, 0);
8053             return;
8054           case ImpossibleMove:
8055             /* bug? */
8056             sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
8057   if (appData.debugMode) {
8058     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8059     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8060     setbuf(debugFP, NULL);
8061   }
8062             DisplayError(buf, 0);
8063             return;
8064           case (ChessMove) 0:   /* end of file */
8065             if (boardIndex < backwardMostMove) {
8066                 /* Oops, gap.  How did that happen? */
8067                 DisplayError(_("Gap in move list"), 0);
8068                 return;
8069             }
8070             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8071             if (boardIndex > forwardMostMove) {
8072                 forwardMostMove = boardIndex;
8073             }
8074             return;
8075           case ElapsedTime:
8076             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8077                 strcat(parseList[boardIndex-1], " ");
8078                 strcat(parseList[boardIndex-1], yy_text);
8079             }
8080             continue;
8081           case Comment:
8082           case PGNTag:
8083           case NAG:
8084           default:
8085             /* ignore */
8086             continue;
8087           case WhiteWins:
8088           case BlackWins:
8089           case GameIsDrawn:
8090           case GameUnfinished:
8091             if (gameMode == IcsExamining) {
8092                 if (boardIndex < backwardMostMove) {
8093                     /* Oops, gap.  How did that happen? */
8094                     return;
8095                 }
8096                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8097                 return;
8098             }
8099             gameInfo.result = moveType;
8100             p = strchr(yy_text, '{');
8101             if (p == NULL) p = strchr(yy_text, '(');
8102             if (p == NULL) {
8103                 p = yy_text;
8104                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8105             } else {
8106                 q = strchr(p, *p == '{' ? '}' : ')');
8107                 if (q != NULL) *q = NULLCHAR;
8108                 p++;
8109             }
8110             gameInfo.resultDetails = StrSave(p);
8111             continue;
8112         }
8113         if (boardIndex >= forwardMostMove &&
8114             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8115             backwardMostMove = blackPlaysFirst ? 1 : 0;
8116             return;
8117         }
8118         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8119                                  fromY, fromX, toY, toX, promoChar,
8120                                  parseList[boardIndex]);
8121         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8122         /* currentMoveString is set as a side-effect of yylex */
8123         strcpy(moveList[boardIndex], currentMoveString);
8124         strcat(moveList[boardIndex], "\n");
8125         boardIndex++;
8126         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8127         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8128           case MT_NONE:
8129           case MT_STALEMATE:
8130           default:
8131             break;
8132           case MT_CHECK:
8133             if(gameInfo.variant != VariantShogi)
8134                 strcat(parseList[boardIndex - 1], "+");
8135             break;
8136           case MT_CHECKMATE:
8137           case MT_STAINMATE:
8138             strcat(parseList[boardIndex - 1], "#");
8139             break;
8140         }
8141     }
8142 }
8143
8144
8145 /* Apply a move to the given board  */
8146 void
8147 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8148      int fromX, fromY, toX, toY;
8149      int promoChar;
8150      Board board;
8151 {
8152   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8153   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8154
8155     /* [HGM] compute & store e.p. status and castling rights for new position */
8156     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8157     { int i;
8158
8159       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8160       oldEP = (signed char)board[EP_STATUS];
8161       board[EP_STATUS] = EP_NONE;
8162
8163       if( board[toY][toX] != EmptySquare ) 
8164            board[EP_STATUS] = EP_CAPTURE;  
8165
8166       if( board[fromY][fromX] == WhitePawn ) {
8167            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8168                board[EP_STATUS] = EP_PAWN_MOVE;
8169            if( toY-fromY==2) {
8170                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8171                         gameInfo.variant != VariantBerolina || toX < fromX)
8172                       board[EP_STATUS] = toX | berolina;
8173                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8174                         gameInfo.variant != VariantBerolina || toX > fromX) 
8175                       board[EP_STATUS] = toX;
8176            }
8177       } else 
8178       if( board[fromY][fromX] == BlackPawn ) {
8179            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8180                board[EP_STATUS] = EP_PAWN_MOVE; 
8181            if( toY-fromY== -2) {
8182                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8183                         gameInfo.variant != VariantBerolina || toX < fromX)
8184                       board[EP_STATUS] = toX | berolina;
8185                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8186                         gameInfo.variant != VariantBerolina || toX > fromX) 
8187                       board[EP_STATUS] = toX;
8188            }
8189        }
8190
8191        for(i=0; i<nrCastlingRights; i++) {
8192            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8193               board[CASTLING][i] == toX   && castlingRank[i] == toY   
8194              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8195        }
8196
8197     }
8198
8199   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8200   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8201        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8202          
8203   if (fromX == toX && fromY == toY) return;
8204
8205   if (fromY == DROP_RANK) {
8206         /* must be first */
8207         piece = board[toY][toX] = (ChessSquare) fromX;
8208   } else {
8209      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8210      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8211      if(gameInfo.variant == VariantKnightmate)
8212          king += (int) WhiteUnicorn - (int) WhiteKing;
8213
8214     /* Code added by Tord: */
8215     /* FRC castling assumed when king captures friendly rook. */
8216     if (board[fromY][fromX] == WhiteKing &&
8217              board[toY][toX] == WhiteRook) {
8218       board[fromY][fromX] = EmptySquare;
8219       board[toY][toX] = EmptySquare;
8220       if(toX > fromX) {
8221         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8222       } else {
8223         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8224       }
8225     } else if (board[fromY][fromX] == BlackKing &&
8226                board[toY][toX] == BlackRook) {
8227       board[fromY][fromX] = EmptySquare;
8228       board[toY][toX] = EmptySquare;
8229       if(toX > fromX) {
8230         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8231       } else {
8232         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8233       }
8234     /* End of code added by Tord */
8235
8236     } else if (board[fromY][fromX] == king
8237         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8238         && toY == fromY && toX > fromX+1) {
8239         board[fromY][fromX] = EmptySquare;
8240         board[toY][toX] = king;
8241         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8242         board[fromY][BOARD_RGHT-1] = EmptySquare;
8243     } else if (board[fromY][fromX] == king
8244         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8245                && toY == fromY && toX < fromX-1) {
8246         board[fromY][fromX] = EmptySquare;
8247         board[toY][toX] = king;
8248         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8249         board[fromY][BOARD_LEFT] = EmptySquare;
8250     } else if (board[fromY][fromX] == WhitePawn
8251                && toY >= BOARD_HEIGHT-promoRank
8252                && gameInfo.variant != VariantXiangqi
8253                ) {
8254         /* white pawn promotion */
8255         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8256         if (board[toY][toX] == EmptySquare) {
8257             board[toY][toX] = WhiteQueen;
8258         }
8259         if(gameInfo.variant==VariantBughouse ||
8260            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8261             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8262         board[fromY][fromX] = EmptySquare;
8263     } else if ((fromY == BOARD_HEIGHT-4)
8264                && (toX != fromX)
8265                && gameInfo.variant != VariantXiangqi
8266                && gameInfo.variant != VariantBerolina
8267                && (board[fromY][fromX] == WhitePawn)
8268                && (board[toY][toX] == EmptySquare)) {
8269         board[fromY][fromX] = EmptySquare;
8270         board[toY][toX] = WhitePawn;
8271         captured = board[toY - 1][toX];
8272         board[toY - 1][toX] = EmptySquare;
8273     } else if ((fromY == BOARD_HEIGHT-4)
8274                && (toX == fromX)
8275                && gameInfo.variant == VariantBerolina
8276                && (board[fromY][fromX] == WhitePawn)
8277                && (board[toY][toX] == EmptySquare)) {
8278         board[fromY][fromX] = EmptySquare;
8279         board[toY][toX] = WhitePawn;
8280         if(oldEP & EP_BEROLIN_A) {
8281                 captured = board[fromY][fromX-1];
8282                 board[fromY][fromX-1] = EmptySquare;
8283         }else{  captured = board[fromY][fromX+1];
8284                 board[fromY][fromX+1] = EmptySquare;
8285         }
8286     } else if (board[fromY][fromX] == king
8287         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8288                && toY == fromY && toX > fromX+1) {
8289         board[fromY][fromX] = EmptySquare;
8290         board[toY][toX] = king;
8291         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8292         board[fromY][BOARD_RGHT-1] = EmptySquare;
8293     } else if (board[fromY][fromX] == king
8294         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8295                && toY == fromY && toX < fromX-1) {
8296         board[fromY][fromX] = EmptySquare;
8297         board[toY][toX] = king;
8298         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8299         board[fromY][BOARD_LEFT] = EmptySquare;
8300     } else if (fromY == 7 && fromX == 3
8301                && board[fromY][fromX] == BlackKing
8302                && toY == 7 && toX == 5) {
8303         board[fromY][fromX] = EmptySquare;
8304         board[toY][toX] = BlackKing;
8305         board[fromY][7] = EmptySquare;
8306         board[toY][4] = BlackRook;
8307     } else if (fromY == 7 && fromX == 3
8308                && board[fromY][fromX] == BlackKing
8309                && toY == 7 && toX == 1) {
8310         board[fromY][fromX] = EmptySquare;
8311         board[toY][toX] = BlackKing;
8312         board[fromY][0] = EmptySquare;
8313         board[toY][2] = BlackRook;
8314     } else if (board[fromY][fromX] == BlackPawn
8315                && toY < promoRank
8316                && gameInfo.variant != VariantXiangqi
8317                ) {
8318         /* black pawn promotion */
8319         board[toY][toX] = CharToPiece(ToLower(promoChar));
8320         if (board[toY][toX] == EmptySquare) {
8321             board[toY][toX] = BlackQueen;
8322         }
8323         if(gameInfo.variant==VariantBughouse ||
8324            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8325             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8326         board[fromY][fromX] = EmptySquare;
8327     } else if ((fromY == 3)
8328                && (toX != fromX)
8329                && gameInfo.variant != VariantXiangqi
8330                && gameInfo.variant != VariantBerolina
8331                && (board[fromY][fromX] == BlackPawn)
8332                && (board[toY][toX] == EmptySquare)) {
8333         board[fromY][fromX] = EmptySquare;
8334         board[toY][toX] = BlackPawn;
8335         captured = board[toY + 1][toX];
8336         board[toY + 1][toX] = EmptySquare;
8337     } else if ((fromY == 3)
8338                && (toX == fromX)
8339                && gameInfo.variant == VariantBerolina
8340                && (board[fromY][fromX] == BlackPawn)
8341                && (board[toY][toX] == EmptySquare)) {
8342         board[fromY][fromX] = EmptySquare;
8343         board[toY][toX] = BlackPawn;
8344         if(oldEP & EP_BEROLIN_A) {
8345                 captured = board[fromY][fromX-1];
8346                 board[fromY][fromX-1] = EmptySquare;
8347         }else{  captured = board[fromY][fromX+1];
8348                 board[fromY][fromX+1] = EmptySquare;
8349         }
8350     } else {
8351         board[toY][toX] = board[fromY][fromX];
8352         board[fromY][fromX] = EmptySquare;
8353     }
8354
8355     /* [HGM] now we promote for Shogi, if needed */
8356     if(gameInfo.variant == VariantShogi && promoChar == 'q')
8357         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8358   }
8359
8360     if (gameInfo.holdingsWidth != 0) {
8361
8362       /* !!A lot more code needs to be written to support holdings  */
8363       /* [HGM] OK, so I have written it. Holdings are stored in the */
8364       /* penultimate board files, so they are automaticlly stored   */
8365       /* in the game history.                                       */
8366       if (fromY == DROP_RANK) {
8367         /* Delete from holdings, by decreasing count */
8368         /* and erasing image if necessary            */
8369         p = (int) fromX;
8370         if(p < (int) BlackPawn) { /* white drop */
8371              p -= (int)WhitePawn;
8372                  p = PieceToNumber((ChessSquare)p);
8373              if(p >= gameInfo.holdingsSize) p = 0;
8374              if(--board[p][BOARD_WIDTH-2] <= 0)
8375                   board[p][BOARD_WIDTH-1] = EmptySquare;
8376              if((int)board[p][BOARD_WIDTH-2] < 0)
8377                         board[p][BOARD_WIDTH-2] = 0;
8378         } else {                  /* black drop */
8379              p -= (int)BlackPawn;
8380                  p = PieceToNumber((ChessSquare)p);
8381              if(p >= gameInfo.holdingsSize) p = 0;
8382              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8383                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8384              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8385                         board[BOARD_HEIGHT-1-p][1] = 0;
8386         }
8387       }
8388       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8389           && gameInfo.variant != VariantBughouse        ) {
8390         /* [HGM] holdings: Add to holdings, if holdings exist */
8391         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { 
8392                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8393                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8394         }
8395         p = (int) captured;
8396         if (p >= (int) BlackPawn) {
8397           p -= (int)BlackPawn;
8398           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8399                   /* in Shogi restore piece to its original  first */
8400                   captured = (ChessSquare) (DEMOTED captured);
8401                   p = DEMOTED p;
8402           }
8403           p = PieceToNumber((ChessSquare)p);
8404           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8405           board[p][BOARD_WIDTH-2]++;
8406           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8407         } else {
8408           p -= (int)WhitePawn;
8409           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8410                   captured = (ChessSquare) (DEMOTED captured);
8411                   p = DEMOTED p;
8412           }
8413           p = PieceToNumber((ChessSquare)p);
8414           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8415           board[BOARD_HEIGHT-1-p][1]++;
8416           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8417         }
8418       }
8419     } else if (gameInfo.variant == VariantAtomic) {
8420       if (captured != EmptySquare) {
8421         int y, x;
8422         for (y = toY-1; y <= toY+1; y++) {
8423           for (x = toX-1; x <= toX+1; x++) {
8424             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8425                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8426               board[y][x] = EmptySquare;
8427             }
8428           }
8429         }
8430         board[toY][toX] = EmptySquare;
8431       }
8432     }
8433     if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8434         /* [HGM] Shogi promotions */
8435         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8436     }
8437
8438     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8439                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8440         // [HGM] superchess: take promotion piece out of holdings
8441         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8442         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8443             if(!--board[k][BOARD_WIDTH-2])
8444                 board[k][BOARD_WIDTH-1] = EmptySquare;
8445         } else {
8446             if(!--board[BOARD_HEIGHT-1-k][1])
8447                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8448         }
8449     }
8450
8451 }
8452
8453 /* Updates forwardMostMove */
8454 void
8455 MakeMove(fromX, fromY, toX, toY, promoChar)
8456      int fromX, fromY, toX, toY;
8457      int promoChar;
8458 {
8459 //    forwardMostMove++; // [HGM] bare: moved downstream
8460
8461     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8462         int timeLeft; static int lastLoadFlag=0; int king, piece;
8463         piece = boards[forwardMostMove][fromY][fromX];
8464         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8465         if(gameInfo.variant == VariantKnightmate)
8466             king += (int) WhiteUnicorn - (int) WhiteKing;
8467         if(forwardMostMove == 0) {
8468             if(blackPlaysFirst) 
8469                 fprintf(serverMoves, "%s;", second.tidy);
8470             fprintf(serverMoves, "%s;", first.tidy);
8471             if(!blackPlaysFirst) 
8472                 fprintf(serverMoves, "%s;", second.tidy);
8473         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8474         lastLoadFlag = loadFlag;
8475         // print base move
8476         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8477         // print castling suffix
8478         if( toY == fromY && piece == king ) {
8479             if(toX-fromX > 1)
8480                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8481             if(fromX-toX >1)
8482                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8483         }
8484         // e.p. suffix
8485         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8486              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8487              boards[forwardMostMove][toY][toX] == EmptySquare
8488              && fromX != toX )
8489                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8490         // promotion suffix
8491         if(promoChar != NULLCHAR)
8492                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8493         if(!loadFlag) {
8494             fprintf(serverMoves, "/%d/%d",
8495                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8496             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8497             else                      timeLeft = blackTimeRemaining/1000;
8498             fprintf(serverMoves, "/%d", timeLeft);
8499         }
8500         fflush(serverMoves);
8501     }
8502
8503     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8504       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8505                         0, 1);
8506       return;
8507     }
8508     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8509     if (commentList[forwardMostMove+1] != NULL) {
8510         free(commentList[forwardMostMove+1]);
8511         commentList[forwardMostMove+1] = NULL;
8512     }
8513     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8514     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8515     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8516     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8517     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8518     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8519     gameInfo.result = GameUnfinished;
8520     if (gameInfo.resultDetails != NULL) {
8521         free(gameInfo.resultDetails);
8522         gameInfo.resultDetails = NULL;
8523     }
8524     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8525                               moveList[forwardMostMove - 1]);
8526     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8527                              PosFlags(forwardMostMove - 1),
8528                              fromY, fromX, toY, toX, promoChar,
8529                              parseList[forwardMostMove - 1]);
8530     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8531       case MT_NONE:
8532       case MT_STALEMATE:
8533       default:
8534         break;
8535       case MT_CHECK:
8536         if(gameInfo.variant != VariantShogi)
8537             strcat(parseList[forwardMostMove - 1], "+");
8538         break;
8539       case MT_CHECKMATE:
8540       case MT_STAINMATE:
8541         strcat(parseList[forwardMostMove - 1], "#");
8542         break;
8543     }
8544     if (appData.debugMode) {
8545         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8546     }
8547
8548 }
8549
8550 /* Updates currentMove if not pausing */
8551 void
8552 ShowMove(fromX, fromY, toX, toY)
8553 {
8554     int instant = (gameMode == PlayFromGameFile) ?
8555         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8556     if(appData.noGUI) return;
8557     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8558         if (!instant) {
8559             if (forwardMostMove == currentMove + 1) {
8560                 AnimateMove(boards[forwardMostMove - 1],
8561                             fromX, fromY, toX, toY);
8562             }
8563             if (appData.highlightLastMove) {
8564                 SetHighlights(fromX, fromY, toX, toY);
8565             }
8566         }
8567         currentMove = forwardMostMove;
8568     }
8569
8570     if (instant) return;
8571
8572     DisplayMove(currentMove - 1);
8573     DrawPosition(FALSE, boards[currentMove]);
8574     DisplayBothClocks();
8575     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8576 }
8577
8578 void SendEgtPath(ChessProgramState *cps)
8579 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8580         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8581
8582         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8583
8584         while(*p) {
8585             char c, *q = name+1, *r, *s;
8586
8587             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8588             while(*p && *p != ',') *q++ = *p++;
8589             *q++ = ':'; *q = 0;
8590             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && 
8591                 strcmp(name, ",nalimov:") == 0 ) {
8592                 // take nalimov path from the menu-changeable option first, if it is defined
8593                 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8594                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8595             } else
8596             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8597                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8598                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8599                 s = r = StrStr(s, ":") + 1; // beginning of path info
8600                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8601                 c = *r; *r = 0;             // temporarily null-terminate path info
8602                     *--q = 0;               // strip of trailig ':' from name
8603                     sprintf(buf, "egtpath %s %s\n", name+1, s);
8604                 *r = c;
8605                 SendToProgram(buf,cps);     // send egtbpath command for this format
8606             }
8607             if(*p == ',') p++; // read away comma to position for next format name
8608         }
8609 }
8610
8611 void
8612 InitChessProgram(cps, setup)
8613      ChessProgramState *cps;
8614      int setup; /* [HGM] needed to setup FRC opening position */
8615 {
8616     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8617     if (appData.noChessProgram) return;
8618     hintRequested = FALSE;
8619     bookRequested = FALSE;
8620
8621     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8622     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8623     if(cps->memSize) { /* [HGM] memory */
8624         sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8625         SendToProgram(buf, cps);
8626     }
8627     SendEgtPath(cps); /* [HGM] EGT */
8628     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8629         sprintf(buf, "cores %d\n", appData.smpCores);
8630         SendToProgram(buf, cps);
8631     }
8632
8633     SendToProgram(cps->initString, cps);
8634     if (gameInfo.variant != VariantNormal &&
8635         gameInfo.variant != VariantLoadable
8636         /* [HGM] also send variant if board size non-standard */
8637         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8638                                             ) {
8639       char *v = VariantName(gameInfo.variant);
8640       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8641         /* [HGM] in protocol 1 we have to assume all variants valid */
8642         sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8643         DisplayFatalError(buf, 0, 1);
8644         return;
8645       }
8646
8647       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8648       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8649       if( gameInfo.variant == VariantXiangqi )
8650            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8651       if( gameInfo.variant == VariantShogi )
8652            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8653       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8654            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8655       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || 
8656                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8657            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8658       if( gameInfo.variant == VariantCourier )
8659            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8660       if( gameInfo.variant == VariantSuper )
8661            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8662       if( gameInfo.variant == VariantGreat )
8663            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8664
8665       if(overruled) {
8666            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, 
8667                                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8668            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8669            if(StrStr(cps->variants, b) == NULL) { 
8670                // specific sized variant not known, check if general sizing allowed
8671                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8672                    if(StrStr(cps->variants, "boardsize") == NULL) {
8673                        sprintf(buf, "Board size %dx%d+%d not supported by %s",
8674                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8675                        DisplayFatalError(buf, 0, 1);
8676                        return;
8677                    }
8678                    /* [HGM] here we really should compare with the maximum supported board size */
8679                }
8680            }
8681       } else sprintf(b, "%s", VariantName(gameInfo.variant));
8682       sprintf(buf, "variant %s\n", b);
8683       SendToProgram(buf, cps);
8684     }
8685     currentlyInitializedVariant = gameInfo.variant;
8686
8687     /* [HGM] send opening position in FRC to first engine */
8688     if(setup) {
8689           SendToProgram("force\n", cps);
8690           SendBoard(cps, 0);
8691           /* engine is now in force mode! Set flag to wake it up after first move. */
8692           setboardSpoiledMachineBlack = 1;
8693     }
8694
8695     if (cps->sendICS) {
8696       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8697       SendToProgram(buf, cps);
8698     }
8699     cps->maybeThinking = FALSE;
8700     cps->offeredDraw = 0;
8701     if (!appData.icsActive) {
8702         SendTimeControl(cps, movesPerSession, timeControl,
8703                         timeIncrement, appData.searchDepth,
8704                         searchTime);
8705     }
8706     if (appData.showThinking 
8707         // [HGM] thinking: four options require thinking output to be sent
8708         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8709                                 ) {
8710         SendToProgram("post\n", cps);
8711     }
8712     SendToProgram("hard\n", cps);
8713     if (!appData.ponderNextMove) {
8714         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8715            it without being sure what state we are in first.  "hard"
8716            is not a toggle, so that one is OK.
8717          */
8718         SendToProgram("easy\n", cps);
8719     }
8720     if (cps->usePing) {
8721       sprintf(buf, "ping %d\n", ++cps->lastPing);
8722       SendToProgram(buf, cps);
8723     }
8724     cps->initDone = TRUE;
8725 }   
8726
8727
8728 void
8729 StartChessProgram(cps)
8730      ChessProgramState *cps;
8731 {
8732     char buf[MSG_SIZ];
8733     int err;
8734
8735     if (appData.noChessProgram) return;
8736     cps->initDone = FALSE;
8737
8738     if (strcmp(cps->host, "localhost") == 0) {
8739         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8740     } else if (*appData.remoteShell == NULLCHAR) {
8741         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8742     } else {
8743         if (*appData.remoteUser == NULLCHAR) {
8744           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8745                     cps->program);
8746         } else {
8747           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8748                     cps->host, appData.remoteUser, cps->program);
8749         }
8750         err = StartChildProcess(buf, "", &cps->pr);
8751     }
8752     
8753     if (err != 0) {
8754         sprintf(buf, _("Startup failure on '%s'"), cps->program);
8755         DisplayFatalError(buf, err, 1);
8756         cps->pr = NoProc;
8757         cps->isr = NULL;
8758         return;
8759     }
8760     
8761     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8762     if (cps->protocolVersion > 1) {
8763       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8764       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8765       cps->comboCnt = 0;  //                and values of combo boxes
8766       SendToProgram(buf, cps);
8767     } else {
8768       SendToProgram("xboard\n", cps);
8769     }
8770 }
8771
8772
8773 void
8774 TwoMachinesEventIfReady P((void))
8775 {
8776   if (first.lastPing != first.lastPong) {
8777     DisplayMessage("", _("Waiting for first chess program"));
8778     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8779     return;
8780   }
8781   if (second.lastPing != second.lastPong) {
8782     DisplayMessage("", _("Waiting for second chess program"));
8783     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8784     return;
8785   }
8786   ThawUI();
8787   TwoMachinesEvent();
8788 }
8789
8790 void
8791 NextMatchGame P((void))
8792 {
8793     int index; /* [HGM] autoinc: step load index during match */
8794     Reset(FALSE, TRUE);
8795     if (*appData.loadGameFile != NULLCHAR) {
8796         index = appData.loadGameIndex;
8797         if(index < 0) { // [HGM] autoinc
8798             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8799             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8800         } 
8801         LoadGameFromFile(appData.loadGameFile,
8802                          index,
8803                          appData.loadGameFile, FALSE);
8804     } else if (*appData.loadPositionFile != NULLCHAR) {
8805         index = appData.loadPositionIndex;
8806         if(index < 0) { // [HGM] autoinc
8807             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8808             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8809         } 
8810         LoadPositionFromFile(appData.loadPositionFile,
8811                              index,
8812                              appData.loadPositionFile);
8813     }
8814     TwoMachinesEventIfReady();
8815 }
8816
8817 void UserAdjudicationEvent( int result )
8818 {
8819     ChessMove gameResult = GameIsDrawn;
8820
8821     if( result > 0 ) {
8822         gameResult = WhiteWins;
8823     }
8824     else if( result < 0 ) {
8825         gameResult = BlackWins;
8826     }
8827
8828     if( gameMode == TwoMachinesPlay ) {
8829         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8830     }
8831 }
8832
8833
8834 // [HGM] save: calculate checksum of game to make games easily identifiable
8835 int StringCheckSum(char *s)
8836 {
8837         int i = 0;
8838         if(s==NULL) return 0;
8839         while(*s) i = i*259 + *s++;
8840         return i;
8841 }
8842
8843 int GameCheckSum()
8844 {
8845         int i, sum=0;
8846         for(i=backwardMostMove; i<forwardMostMove; i++) {
8847                 sum += pvInfoList[i].depth;
8848                 sum += StringCheckSum(parseList[i]);
8849                 sum += StringCheckSum(commentList[i]);
8850                 sum *= 261;
8851         }
8852         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8853         return sum + StringCheckSum(commentList[i]);
8854 } // end of save patch
8855
8856 void
8857 GameEnds(result, resultDetails, whosays)
8858      ChessMove result;
8859      char *resultDetails;
8860      int whosays;
8861 {
8862     GameMode nextGameMode;
8863     int isIcsGame;
8864     char buf[MSG_SIZ];
8865
8866     if(endingGame) return; /* [HGM] crash: forbid recursion */
8867     endingGame = 1;
8868
8869     if (appData.debugMode) {
8870       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8871               result, resultDetails ? resultDetails : "(null)", whosays);
8872     }
8873
8874     fromX = fromY = -1; // [HGM] abort any move the user is entering.
8875
8876     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8877         /* If we are playing on ICS, the server decides when the
8878            game is over, but the engine can offer to draw, claim 
8879            a draw, or resign. 
8880          */
8881 #if ZIPPY
8882         if (appData.zippyPlay && first.initDone) {
8883             if (result == GameIsDrawn) {
8884                 /* In case draw still needs to be claimed */
8885                 SendToICS(ics_prefix);
8886                 SendToICS("draw\n");
8887             } else if (StrCaseStr(resultDetails, "resign")) {
8888                 SendToICS(ics_prefix);
8889                 SendToICS("resign\n");
8890             }
8891         }
8892 #endif
8893         endingGame = 0; /* [HGM] crash */
8894         return;
8895     }
8896
8897     /* If we're loading the game from a file, stop */
8898     if (whosays == GE_FILE) {
8899       (void) StopLoadGameTimer();
8900       gameFileFP = NULL;
8901     }
8902
8903     /* Cancel draw offers */
8904     first.offeredDraw = second.offeredDraw = 0;
8905
8906     /* If this is an ICS game, only ICS can really say it's done;
8907        if not, anyone can. */
8908     isIcsGame = (gameMode == IcsPlayingWhite || 
8909                  gameMode == IcsPlayingBlack || 
8910                  gameMode == IcsObserving    || 
8911                  gameMode == IcsExamining);
8912
8913     if (!isIcsGame || whosays == GE_ICS) {
8914         /* OK -- not an ICS game, or ICS said it was done */
8915         StopClocks();
8916         if (!isIcsGame && !appData.noChessProgram) 
8917           SetUserThinkingEnables();
8918     
8919         /* [HGM] if a machine claims the game end we verify this claim */
8920         if(gameMode == TwoMachinesPlay && appData.testClaims) {
8921             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8922                 char claimer;
8923                 ChessMove trueResult = (ChessMove) -1;
8924
8925                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
8926                                             first.twoMachinesColor[0] :
8927                                             second.twoMachinesColor[0] ;
8928
8929                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8930                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8931                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8932                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8933                 } else
8934                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8935                     /* [HGM] verify: engine mate claims accepted if they were flagged */
8936                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8937                 } else
8938                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8939                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8940                 }
8941
8942                 // now verify win claims, but not in drop games, as we don't understand those yet
8943                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8944                                                  || gameInfo.variant == VariantGreat) &&
8945                     (result == WhiteWins && claimer == 'w' ||
8946                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
8947                       if (appData.debugMode) {
8948                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
8949                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8950                       }
8951                       if(result != trueResult) {
8952                               sprintf(buf, "False win claim: '%s'", resultDetails);
8953                               result = claimer == 'w' ? BlackWins : WhiteWins;
8954                               resultDetails = buf;
8955                       }
8956                 } else
8957                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8958                     && (forwardMostMove <= backwardMostMove ||
8959                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8960                         (claimer=='b')==(forwardMostMove&1))
8961                                                                                   ) {
8962                       /* [HGM] verify: draws that were not flagged are false claims */
8963                       sprintf(buf, "False draw claim: '%s'", resultDetails);
8964                       result = claimer == 'w' ? BlackWins : WhiteWins;
8965                       resultDetails = buf;
8966                 }
8967                 /* (Claiming a loss is accepted no questions asked!) */
8968             }
8969             /* [HGM] bare: don't allow bare King to win */
8970             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8971                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway 
8972                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8973                && result != GameIsDrawn)
8974             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8975                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8976                         int p = (signed char)boards[forwardMostMove][i][j] - color;
8977                         if(p >= 0 && p <= (int)WhiteKing) k++;
8978                 }
8979                 if (appData.debugMode) {
8980                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8981                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8982                 }
8983                 if(k <= 1) {
8984                         result = GameIsDrawn;
8985                         sprintf(buf, "%s but bare king", resultDetails);
8986                         resultDetails = buf;
8987                 }
8988             }
8989         }
8990
8991
8992         if(serverMoves != NULL && !loadFlag) { char c = '=';
8993             if(result==WhiteWins) c = '+';
8994             if(result==BlackWins) c = '-';
8995             if(resultDetails != NULL)
8996                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8997         }
8998         if (resultDetails != NULL) {
8999             gameInfo.result = result;
9000             gameInfo.resultDetails = StrSave(resultDetails);
9001
9002             /* display last move only if game was not loaded from file */
9003             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9004                 DisplayMove(currentMove - 1);
9005     
9006             if (forwardMostMove != 0) {
9007                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9008                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9009                                                                 ) {
9010                     if (*appData.saveGameFile != NULLCHAR) {
9011                         SaveGameToFile(appData.saveGameFile, TRUE);
9012                     } else if (appData.autoSaveGames) {
9013                         AutoSaveGame();
9014                     }
9015                     if (*appData.savePositionFile != NULLCHAR) {
9016                         SavePositionToFile(appData.savePositionFile);
9017                     }
9018                 }
9019             }
9020
9021             /* Tell program how game ended in case it is learning */
9022             /* [HGM] Moved this to after saving the PGN, just in case */
9023             /* engine died and we got here through time loss. In that */
9024             /* case we will get a fatal error writing the pipe, which */
9025             /* would otherwise lose us the PGN.                       */
9026             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9027             /* output during GameEnds should never be fatal anymore   */
9028             if (gameMode == MachinePlaysWhite ||
9029                 gameMode == MachinePlaysBlack ||
9030                 gameMode == TwoMachinesPlay ||
9031                 gameMode == IcsPlayingWhite ||
9032                 gameMode == IcsPlayingBlack ||
9033                 gameMode == BeginningOfGame) {
9034                 char buf[MSG_SIZ];
9035                 sprintf(buf, "result %s {%s}\n", PGNResult(result),
9036                         resultDetails);
9037                 if (first.pr != NoProc) {
9038                     SendToProgram(buf, &first);
9039                 }
9040                 if (second.pr != NoProc &&
9041                     gameMode == TwoMachinesPlay) {
9042                     SendToProgram(buf, &second);
9043                 }
9044             }
9045         }
9046
9047         if (appData.icsActive) {
9048             if (appData.quietPlay &&
9049                 (gameMode == IcsPlayingWhite ||
9050                  gameMode == IcsPlayingBlack)) {
9051                 SendToICS(ics_prefix);
9052                 SendToICS("set shout 1\n");
9053             }
9054             nextGameMode = IcsIdle;
9055             ics_user_moved = FALSE;
9056             /* clean up premove.  It's ugly when the game has ended and the
9057              * premove highlights are still on the board.
9058              */
9059             if (gotPremove) {
9060               gotPremove = FALSE;
9061               ClearPremoveHighlights();
9062               DrawPosition(FALSE, boards[currentMove]);
9063             }
9064             if (whosays == GE_ICS) {
9065                 switch (result) {
9066                 case WhiteWins:
9067                     if (gameMode == IcsPlayingWhite)
9068                         PlayIcsWinSound();
9069                     else if(gameMode == IcsPlayingBlack)
9070                         PlayIcsLossSound();
9071                     break;
9072                 case BlackWins:
9073                     if (gameMode == IcsPlayingBlack)
9074                         PlayIcsWinSound();
9075                     else if(gameMode == IcsPlayingWhite)
9076                         PlayIcsLossSound();
9077                     break;
9078                 case GameIsDrawn:
9079                     PlayIcsDrawSound();
9080                     break;
9081                 default:
9082                     PlayIcsUnfinishedSound();
9083                 }
9084             }
9085         } else if (gameMode == EditGame ||
9086                    gameMode == PlayFromGameFile || 
9087                    gameMode == AnalyzeMode || 
9088                    gameMode == AnalyzeFile) {
9089             nextGameMode = gameMode;
9090         } else {
9091             nextGameMode = EndOfGame;
9092         }
9093         pausing = FALSE;
9094         ModeHighlight();
9095     } else {
9096         nextGameMode = gameMode;
9097     }
9098
9099     if (appData.noChessProgram) {
9100         gameMode = nextGameMode;
9101         ModeHighlight();
9102         endingGame = 0; /* [HGM] crash */
9103         return;
9104     }
9105
9106     if (first.reuse) {
9107         /* Put first chess program into idle state */
9108         if (first.pr != NoProc &&
9109             (gameMode == MachinePlaysWhite ||
9110              gameMode == MachinePlaysBlack ||
9111              gameMode == TwoMachinesPlay ||
9112              gameMode == IcsPlayingWhite ||
9113              gameMode == IcsPlayingBlack ||
9114              gameMode == BeginningOfGame)) {
9115             SendToProgram("force\n", &first);
9116             if (first.usePing) {
9117               char buf[MSG_SIZ];
9118               sprintf(buf, "ping %d\n", ++first.lastPing);
9119               SendToProgram(buf, &first);
9120             }
9121         }
9122     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9123         /* Kill off first chess program */
9124         if (first.isr != NULL)
9125           RemoveInputSource(first.isr);
9126         first.isr = NULL;
9127     
9128         if (first.pr != NoProc) {
9129             ExitAnalyzeMode();
9130             DoSleep( appData.delayBeforeQuit );
9131             SendToProgram("quit\n", &first);
9132             DoSleep( appData.delayAfterQuit );
9133             DestroyChildProcess(first.pr, first.useSigterm);
9134         }
9135         first.pr = NoProc;
9136     }
9137     if (second.reuse) {
9138         /* Put second chess program into idle state */
9139         if (second.pr != NoProc &&
9140             gameMode == TwoMachinesPlay) {
9141             SendToProgram("force\n", &second);
9142             if (second.usePing) {
9143               char buf[MSG_SIZ];
9144               sprintf(buf, "ping %d\n", ++second.lastPing);
9145               SendToProgram(buf, &second);
9146             }
9147         }
9148     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9149         /* Kill off second chess program */
9150         if (second.isr != NULL)
9151           RemoveInputSource(second.isr);
9152         second.isr = NULL;
9153     
9154         if (second.pr != NoProc) {
9155             DoSleep( appData.delayBeforeQuit );
9156             SendToProgram("quit\n", &second);
9157             DoSleep( appData.delayAfterQuit );
9158             DestroyChildProcess(second.pr, second.useSigterm);
9159         }
9160         second.pr = NoProc;
9161     }
9162
9163     if (matchMode && gameMode == TwoMachinesPlay) {
9164         switch (result) {
9165         case WhiteWins:
9166           if (first.twoMachinesColor[0] == 'w') {
9167             first.matchWins++;
9168           } else {
9169             second.matchWins++;
9170           }
9171           break;
9172         case BlackWins:
9173           if (first.twoMachinesColor[0] == 'b') {
9174             first.matchWins++;
9175           } else {
9176             second.matchWins++;
9177           }
9178           break;
9179         default:
9180           break;
9181         }
9182         if (matchGame < appData.matchGames) {
9183             char *tmp;
9184             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9185                 tmp = first.twoMachinesColor;
9186                 first.twoMachinesColor = second.twoMachinesColor;
9187                 second.twoMachinesColor = tmp;
9188             }
9189             gameMode = nextGameMode;
9190             matchGame++;
9191             if(appData.matchPause>10000 || appData.matchPause<10)
9192                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9193             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9194             endingGame = 0; /* [HGM] crash */
9195             return;
9196         } else {
9197             char buf[MSG_SIZ];
9198             gameMode = nextGameMode;
9199             sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
9200                     first.tidy, second.tidy,
9201                     first.matchWins, second.matchWins,
9202                     appData.matchGames - (first.matchWins + second.matchWins));
9203             DisplayFatalError(buf, 0, 0);
9204         }
9205     }
9206     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9207         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9208       ExitAnalyzeMode();
9209     gameMode = nextGameMode;
9210     ModeHighlight();
9211     endingGame = 0;  /* [HGM] crash */
9212 }
9213
9214 /* Assumes program was just initialized (initString sent).
9215    Leaves program in force mode. */
9216 void
9217 FeedMovesToProgram(cps, upto) 
9218      ChessProgramState *cps;
9219      int upto;
9220 {
9221     int i;
9222     
9223     if (appData.debugMode)
9224       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9225               startedFromSetupPosition ? "position and " : "",
9226               backwardMostMove, upto, cps->which);
9227     if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9228         // [HGM] variantswitch: make engine aware of new variant
9229         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9230                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9231         sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9232         SendToProgram(buf, cps);
9233         currentlyInitializedVariant = gameInfo.variant;
9234     }
9235     SendToProgram("force\n", cps);
9236     if (startedFromSetupPosition) {
9237         SendBoard(cps, backwardMostMove);
9238     if (appData.debugMode) {
9239         fprintf(debugFP, "feedMoves\n");
9240     }
9241     }
9242     for (i = backwardMostMove; i < upto; i++) {
9243         SendMoveToProgram(i, cps);
9244     }
9245 }
9246
9247
9248 void
9249 ResurrectChessProgram()
9250 {
9251      /* The chess program may have exited.
9252         If so, restart it and feed it all the moves made so far. */
9253
9254     if (appData.noChessProgram || first.pr != NoProc) return;
9255     
9256     StartChessProgram(&first);
9257     InitChessProgram(&first, FALSE);
9258     FeedMovesToProgram(&first, currentMove);
9259
9260     if (!first.sendTime) {
9261         /* can't tell gnuchess what its clock should read,
9262            so we bow to its notion. */
9263         ResetClocks();
9264         timeRemaining[0][currentMove] = whiteTimeRemaining;
9265         timeRemaining[1][currentMove] = blackTimeRemaining;
9266     }
9267
9268     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9269                 appData.icsEngineAnalyze) && first.analysisSupport) {
9270       SendToProgram("analyze\n", &first);
9271       first.analyzing = TRUE;
9272     }
9273 }
9274
9275 /*
9276  * Button procedures
9277  */
9278 void
9279 Reset(redraw, init)
9280      int redraw, init;
9281 {
9282     int i;
9283
9284     if (appData.debugMode) {
9285         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9286                 redraw, init, gameMode);
9287     }
9288     CleanupTail(); // [HGM] vari: delete any stored variations
9289     pausing = pauseExamInvalid = FALSE;
9290     startedFromSetupPosition = blackPlaysFirst = FALSE;
9291     firstMove = TRUE;
9292     whiteFlag = blackFlag = FALSE;
9293     userOfferedDraw = FALSE;
9294     hintRequested = bookRequested = FALSE;
9295     first.maybeThinking = FALSE;
9296     second.maybeThinking = FALSE;
9297     first.bookSuspend = FALSE; // [HGM] book
9298     second.bookSuspend = FALSE;
9299     thinkOutput[0] = NULLCHAR;
9300     lastHint[0] = NULLCHAR;
9301     ClearGameInfo(&gameInfo);
9302     gameInfo.variant = StringToVariant(appData.variant);
9303     ics_user_moved = ics_clock_paused = FALSE;
9304     ics_getting_history = H_FALSE;
9305     ics_gamenum = -1;
9306     white_holding[0] = black_holding[0] = NULLCHAR;
9307     ClearProgramStats();
9308     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9309     
9310     ResetFrontEnd();
9311     ClearHighlights();
9312     flipView = appData.flipView;
9313     ClearPremoveHighlights();
9314     gotPremove = FALSE;
9315     alarmSounded = FALSE;
9316
9317     GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9318     if(appData.serverMovesName != NULL) {
9319         /* [HGM] prepare to make moves file for broadcasting */
9320         clock_t t = clock();
9321         if(serverMoves != NULL) fclose(serverMoves);
9322         serverMoves = fopen(appData.serverMovesName, "r");
9323         if(serverMoves != NULL) {
9324             fclose(serverMoves);
9325             /* delay 15 sec before overwriting, so all clients can see end */
9326             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9327         }
9328         serverMoves = fopen(appData.serverMovesName, "w");
9329     }
9330
9331     ExitAnalyzeMode();
9332     gameMode = BeginningOfGame;
9333     ModeHighlight();
9334     if(appData.icsActive) gameInfo.variant = VariantNormal;
9335     currentMove = forwardMostMove = backwardMostMove = 0;
9336     InitPosition(redraw);
9337     for (i = 0; i < MAX_MOVES; i++) {
9338         if (commentList[i] != NULL) {
9339             free(commentList[i]);
9340             commentList[i] = NULL;
9341         }
9342     }
9343     ResetClocks();
9344     timeRemaining[0][0] = whiteTimeRemaining;
9345     timeRemaining[1][0] = blackTimeRemaining;
9346     if (first.pr == NULL) {
9347         StartChessProgram(&first);
9348     }
9349     if (init) {
9350             InitChessProgram(&first, startedFromSetupPosition);
9351     }
9352     DisplayTitle("");
9353     DisplayMessage("", "");
9354     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9355     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9356 }
9357
9358 void
9359 AutoPlayGameLoop()
9360 {
9361     for (;;) {
9362         if (!AutoPlayOneMove())
9363           return;
9364         if (matchMode || appData.timeDelay == 0)
9365           continue;
9366         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9367           return;
9368         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9369         break;
9370     }
9371 }
9372
9373
9374 int
9375 AutoPlayOneMove()
9376 {
9377     int fromX, fromY, toX, toY;
9378
9379     if (appData.debugMode) {
9380       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9381     }
9382
9383     if (gameMode != PlayFromGameFile)
9384       return FALSE;
9385
9386     if (currentMove >= forwardMostMove) {
9387       gameMode = EditGame;
9388       ModeHighlight();
9389
9390       /* [AS] Clear current move marker at the end of a game */
9391       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9392
9393       return FALSE;
9394     }
9395     
9396     toX = moveList[currentMove][2] - AAA;
9397     toY = moveList[currentMove][3] - ONE;
9398
9399     if (moveList[currentMove][1] == '@') {
9400         if (appData.highlightLastMove) {
9401             SetHighlights(-1, -1, toX, toY);
9402         }
9403     } else {
9404         fromX = moveList[currentMove][0] - AAA;
9405         fromY = moveList[currentMove][1] - ONE;
9406
9407         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9408
9409         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9410
9411         if (appData.highlightLastMove) {
9412             SetHighlights(fromX, fromY, toX, toY);
9413         }
9414     }
9415     DisplayMove(currentMove);
9416     SendMoveToProgram(currentMove++, &first);
9417     DisplayBothClocks();
9418     DrawPosition(FALSE, boards[currentMove]);
9419     // [HGM] PV info: always display, routine tests if empty
9420     DisplayComment(currentMove - 1, commentList[currentMove]);
9421     return TRUE;
9422 }
9423
9424
9425 int
9426 LoadGameOneMove(readAhead)
9427      ChessMove readAhead;
9428 {
9429     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9430     char promoChar = NULLCHAR;
9431     ChessMove moveType;
9432     char move[MSG_SIZ];
9433     char *p, *q;
9434     
9435     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && 
9436         gameMode != AnalyzeMode && gameMode != Training) {
9437         gameFileFP = NULL;
9438         return FALSE;
9439     }
9440     
9441     yyboardindex = forwardMostMove;
9442     if (readAhead != (ChessMove)0) {
9443       moveType = readAhead;
9444     } else {
9445       if (gameFileFP == NULL)
9446           return FALSE;
9447       moveType = (ChessMove) yylex();
9448     }
9449     
9450     done = FALSE;
9451     switch (moveType) {
9452       case Comment:
9453         if (appData.debugMode) 
9454           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9455         p = yy_text;
9456
9457         /* append the comment but don't display it */
9458         AppendComment(currentMove, p, FALSE);
9459         return TRUE;
9460
9461       case WhiteCapturesEnPassant:
9462       case BlackCapturesEnPassant:
9463       case WhitePromotionChancellor:
9464       case BlackPromotionChancellor:
9465       case WhitePromotionArchbishop:
9466       case BlackPromotionArchbishop:
9467       case WhitePromotionCentaur:
9468       case BlackPromotionCentaur:
9469       case WhitePromotionQueen:
9470       case BlackPromotionQueen:
9471       case WhitePromotionRook:
9472       case BlackPromotionRook:
9473       case WhitePromotionBishop:
9474       case BlackPromotionBishop:
9475       case WhitePromotionKnight:
9476       case BlackPromotionKnight:
9477       case WhitePromotionKing:
9478       case BlackPromotionKing:
9479       case NormalMove:
9480       case WhiteKingSideCastle:
9481       case WhiteQueenSideCastle:
9482       case BlackKingSideCastle:
9483       case BlackQueenSideCastle:
9484       case WhiteKingSideCastleWild:
9485       case WhiteQueenSideCastleWild:
9486       case BlackKingSideCastleWild:
9487       case BlackQueenSideCastleWild:
9488       /* PUSH Fabien */
9489       case WhiteHSideCastleFR:
9490       case WhiteASideCastleFR:
9491       case BlackHSideCastleFR:
9492       case BlackASideCastleFR:
9493       /* POP Fabien */
9494         if (appData.debugMode)
9495           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9496         fromX = currentMoveString[0] - AAA;
9497         fromY = currentMoveString[1] - ONE;
9498         toX = currentMoveString[2] - AAA;
9499         toY = currentMoveString[3] - ONE;
9500         promoChar = currentMoveString[4];
9501         break;
9502
9503       case WhiteDrop:
9504       case BlackDrop:
9505         if (appData.debugMode)
9506           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9507         fromX = moveType == WhiteDrop ?
9508           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9509         (int) CharToPiece(ToLower(currentMoveString[0]));
9510         fromY = DROP_RANK;
9511         toX = currentMoveString[2] - AAA;
9512         toY = currentMoveString[3] - ONE;
9513         break;
9514
9515       case WhiteWins:
9516       case BlackWins:
9517       case GameIsDrawn:
9518       case GameUnfinished:
9519         if (appData.debugMode)
9520           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9521         p = strchr(yy_text, '{');
9522         if (p == NULL) p = strchr(yy_text, '(');
9523         if (p == NULL) {
9524             p = yy_text;
9525             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9526         } else {
9527             q = strchr(p, *p == '{' ? '}' : ')');
9528             if (q != NULL) *q = NULLCHAR;
9529             p++;
9530         }
9531         GameEnds(moveType, p, GE_FILE);
9532         done = TRUE;
9533         if (cmailMsgLoaded) {
9534             ClearHighlights();
9535             flipView = WhiteOnMove(currentMove);
9536             if (moveType == GameUnfinished) flipView = !flipView;
9537             if (appData.debugMode)
9538               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9539         }
9540         break;
9541
9542       case (ChessMove) 0:       /* end of file */
9543         if (appData.debugMode)
9544           fprintf(debugFP, "Parser hit end of file\n");
9545         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9546           case MT_NONE:
9547           case MT_CHECK:
9548             break;
9549           case MT_CHECKMATE:
9550           case MT_STAINMATE:
9551             if (WhiteOnMove(currentMove)) {
9552                 GameEnds(BlackWins, "Black mates", GE_FILE);
9553             } else {
9554                 GameEnds(WhiteWins, "White mates", GE_FILE);
9555             }
9556             break;
9557           case MT_STALEMATE:
9558             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9559             break;
9560         }
9561         done = TRUE;
9562         break;
9563
9564       case MoveNumberOne:
9565         if (lastLoadGameStart == GNUChessGame) {
9566             /* GNUChessGames have numbers, but they aren't move numbers */
9567             if (appData.debugMode)
9568               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9569                       yy_text, (int) moveType);
9570             return LoadGameOneMove((ChessMove)0); /* tail recursion */
9571         }
9572         /* else fall thru */
9573
9574       case XBoardGame:
9575       case GNUChessGame:
9576       case PGNTag:
9577         /* Reached start of next game in file */
9578         if (appData.debugMode)
9579           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9580         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9581           case MT_NONE:
9582           case MT_CHECK:
9583             break;
9584           case MT_CHECKMATE:
9585           case MT_STAINMATE:
9586             if (WhiteOnMove(currentMove)) {
9587                 GameEnds(BlackWins, "Black mates", GE_FILE);
9588             } else {
9589                 GameEnds(WhiteWins, "White mates", GE_FILE);
9590             }
9591             break;
9592           case MT_STALEMATE:
9593             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9594             break;
9595         }
9596         done = TRUE;
9597         break;
9598
9599       case PositionDiagram:     /* should not happen; ignore */
9600       case ElapsedTime:         /* ignore */
9601       case NAG:                 /* ignore */
9602         if (appData.debugMode)
9603           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9604                   yy_text, (int) moveType);
9605         return LoadGameOneMove((ChessMove)0); /* tail recursion */
9606
9607       case IllegalMove:
9608         if (appData.testLegality) {
9609             if (appData.debugMode)
9610               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9611             sprintf(move, _("Illegal move: %d.%s%s"),
9612                     (forwardMostMove / 2) + 1,
9613                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9614             DisplayError(move, 0);
9615             done = TRUE;
9616         } else {
9617             if (appData.debugMode)
9618               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9619                       yy_text, currentMoveString);
9620             fromX = currentMoveString[0] - AAA;
9621             fromY = currentMoveString[1] - ONE;
9622             toX = currentMoveString[2] - AAA;
9623             toY = currentMoveString[3] - ONE;
9624             promoChar = currentMoveString[4];
9625         }
9626         break;
9627
9628       case AmbiguousMove:
9629         if (appData.debugMode)
9630           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9631         sprintf(move, _("Ambiguous move: %d.%s%s"),
9632                 (forwardMostMove / 2) + 1,
9633                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9634         DisplayError(move, 0);
9635         done = TRUE;
9636         break;
9637
9638       default:
9639       case ImpossibleMove:
9640         if (appData.debugMode)
9641           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9642         sprintf(move, _("Illegal move: %d.%s%s"),
9643                 (forwardMostMove / 2) + 1,
9644                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9645         DisplayError(move, 0);
9646         done = TRUE;
9647         break;
9648     }
9649
9650     if (done) {
9651         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9652             DrawPosition(FALSE, boards[currentMove]);
9653             DisplayBothClocks();
9654             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9655               DisplayComment(currentMove - 1, commentList[currentMove]);
9656         }
9657         (void) StopLoadGameTimer();
9658         gameFileFP = NULL;
9659         cmailOldMove = forwardMostMove;
9660         return FALSE;
9661     } else {
9662         /* currentMoveString is set as a side-effect of yylex */
9663         strcat(currentMoveString, "\n");
9664         strcpy(moveList[forwardMostMove], currentMoveString);
9665         
9666         thinkOutput[0] = NULLCHAR;
9667         MakeMove(fromX, fromY, toX, toY, promoChar);
9668         currentMove = forwardMostMove;
9669         return TRUE;
9670     }
9671 }
9672
9673 /* Load the nth game from the given file */
9674 int
9675 LoadGameFromFile(filename, n, title, useList)
9676      char *filename;
9677      int n;
9678      char *title;
9679      /*Boolean*/ int useList;
9680 {
9681     FILE *f;
9682     char buf[MSG_SIZ];
9683
9684     if (strcmp(filename, "-") == 0) {
9685         f = stdin;
9686         title = "stdin";
9687     } else {
9688         f = fopen(filename, "rb");
9689         if (f == NULL) {
9690           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9691             DisplayError(buf, errno);
9692             return FALSE;
9693         }
9694     }
9695     if (fseek(f, 0, 0) == -1) {
9696         /* f is not seekable; probably a pipe */
9697         useList = FALSE;
9698     }
9699     if (useList && n == 0) {
9700         int error = GameListBuild(f);
9701         if (error) {
9702             DisplayError(_("Cannot build game list"), error);
9703         } else if (!ListEmpty(&gameList) &&
9704                    ((ListGame *) gameList.tailPred)->number > 1) {
9705             GameListPopUp(f, title);
9706             return TRUE;
9707         }
9708         GameListDestroy();
9709         n = 1;
9710     }
9711     if (n == 0) n = 1;
9712     return LoadGame(f, n, title, FALSE);
9713 }
9714
9715
9716 void
9717 MakeRegisteredMove()
9718 {
9719     int fromX, fromY, toX, toY;
9720     char promoChar;
9721     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9722         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9723           case CMAIL_MOVE:
9724           case CMAIL_DRAW:
9725             if (appData.debugMode)
9726               fprintf(debugFP, "Restoring %s for game %d\n",
9727                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9728     
9729             thinkOutput[0] = NULLCHAR;
9730             strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9731             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9732             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9733             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9734             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9735             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9736             MakeMove(fromX, fromY, toX, toY, promoChar);
9737             ShowMove(fromX, fromY, toX, toY);
9738               
9739             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9740               case MT_NONE:
9741               case MT_CHECK:
9742                 break;
9743                 
9744               case MT_CHECKMATE:
9745               case MT_STAINMATE:
9746                 if (WhiteOnMove(currentMove)) {
9747                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9748                 } else {
9749                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9750                 }
9751                 break;
9752                 
9753               case MT_STALEMATE:
9754                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9755                 break;
9756             }
9757
9758             break;
9759             
9760           case CMAIL_RESIGN:
9761             if (WhiteOnMove(currentMove)) {
9762                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9763             } else {
9764                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9765             }
9766             break;
9767             
9768           case CMAIL_ACCEPT:
9769             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9770             break;
9771               
9772           default:
9773             break;
9774         }
9775     }
9776
9777     return;
9778 }
9779
9780 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9781 int
9782 CmailLoadGame(f, gameNumber, title, useList)
9783      FILE *f;
9784      int gameNumber;
9785      char *title;
9786      int useList;
9787 {
9788     int retVal;
9789
9790     if (gameNumber > nCmailGames) {
9791         DisplayError(_("No more games in this message"), 0);
9792         return FALSE;
9793     }
9794     if (f == lastLoadGameFP) {
9795         int offset = gameNumber - lastLoadGameNumber;
9796         if (offset == 0) {
9797             cmailMsg[0] = NULLCHAR;
9798             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9799                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9800                 nCmailMovesRegistered--;
9801             }
9802             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9803             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9804                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9805             }
9806         } else {
9807             if (! RegisterMove()) return FALSE;
9808         }
9809     }
9810
9811     retVal = LoadGame(f, gameNumber, title, useList);
9812
9813     /* Make move registered during previous look at this game, if any */
9814     MakeRegisteredMove();
9815
9816     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9817         commentList[currentMove]
9818           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9819         DisplayComment(currentMove - 1, commentList[currentMove]);
9820     }
9821
9822     return retVal;
9823 }
9824
9825 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9826 int
9827 ReloadGame(offset)
9828      int offset;
9829 {
9830     int gameNumber = lastLoadGameNumber + offset;
9831     if (lastLoadGameFP == NULL) {
9832         DisplayError(_("No game has been loaded yet"), 0);
9833         return FALSE;
9834     }
9835     if (gameNumber <= 0) {
9836         DisplayError(_("Can't back up any further"), 0);
9837         return FALSE;
9838     }
9839     if (cmailMsgLoaded) {
9840         return CmailLoadGame(lastLoadGameFP, gameNumber,
9841                              lastLoadGameTitle, lastLoadGameUseList);
9842     } else {
9843         return LoadGame(lastLoadGameFP, gameNumber,
9844                         lastLoadGameTitle, lastLoadGameUseList);
9845     }
9846 }
9847
9848
9849
9850 /* Load the nth game from open file f */
9851 int
9852 LoadGame(f, gameNumber, title, useList)
9853      FILE *f;
9854      int gameNumber;
9855      char *title;
9856      int useList;
9857 {
9858     ChessMove cm;
9859     char buf[MSG_SIZ];
9860     int gn = gameNumber;
9861     ListGame *lg = NULL;
9862     int numPGNTags = 0;
9863     int err;
9864     GameMode oldGameMode;
9865     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9866
9867     if (appData.debugMode) 
9868         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9869
9870     if (gameMode == Training )
9871         SetTrainingModeOff();
9872
9873     oldGameMode = gameMode;
9874     if (gameMode != BeginningOfGame) {
9875       Reset(FALSE, TRUE);
9876     }
9877
9878     gameFileFP = f;
9879     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9880         fclose(lastLoadGameFP);
9881     }
9882
9883     if (useList) {
9884         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9885         
9886         if (lg) {
9887             fseek(f, lg->offset, 0);
9888             GameListHighlight(gameNumber);
9889             gn = 1;
9890         }
9891         else {
9892             DisplayError(_("Game number out of range"), 0);
9893             return FALSE;
9894         }
9895     } else {
9896         GameListDestroy();
9897         if (fseek(f, 0, 0) == -1) {
9898             if (f == lastLoadGameFP ?
9899                 gameNumber == lastLoadGameNumber + 1 :
9900                 gameNumber == 1) {
9901                 gn = 1;
9902             } else {
9903                 DisplayError(_("Can't seek on game file"), 0);
9904                 return FALSE;
9905             }
9906         }
9907     }
9908     lastLoadGameFP = f;
9909     lastLoadGameNumber = gameNumber;
9910     strcpy(lastLoadGameTitle, title);
9911     lastLoadGameUseList = useList;
9912
9913     yynewfile(f);
9914
9915     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9916       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9917                 lg->gameInfo.black);
9918             DisplayTitle(buf);
9919     } else if (*title != NULLCHAR) {
9920         if (gameNumber > 1) {
9921             sprintf(buf, "%s %d", title, gameNumber);
9922             DisplayTitle(buf);
9923         } else {
9924             DisplayTitle(title);
9925         }
9926     }
9927
9928     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9929         gameMode = PlayFromGameFile;
9930         ModeHighlight();
9931     }
9932
9933     currentMove = forwardMostMove = backwardMostMove = 0;
9934     CopyBoard(boards[0], initialPosition);
9935     StopClocks();
9936
9937     /*
9938      * Skip the first gn-1 games in the file.
9939      * Also skip over anything that precedes an identifiable 
9940      * start of game marker, to avoid being confused by 
9941      * garbage at the start of the file.  Currently 
9942      * recognized start of game markers are the move number "1",
9943      * the pattern "gnuchess .* game", the pattern
9944      * "^[#;%] [^ ]* game file", and a PGN tag block.  
9945      * A game that starts with one of the latter two patterns
9946      * will also have a move number 1, possibly
9947      * following a position diagram.
9948      * 5-4-02: Let's try being more lenient and allowing a game to
9949      * start with an unnumbered move.  Does that break anything?
9950      */
9951     cm = lastLoadGameStart = (ChessMove) 0;
9952     while (gn > 0) {
9953         yyboardindex = forwardMostMove;
9954         cm = (ChessMove) yylex();
9955         switch (cm) {
9956           case (ChessMove) 0:
9957             if (cmailMsgLoaded) {
9958                 nCmailGames = CMAIL_MAX_GAMES - gn;
9959             } else {
9960                 Reset(TRUE, TRUE);
9961                 DisplayError(_("Game not found in file"), 0);
9962             }
9963             return FALSE;
9964
9965           case GNUChessGame:
9966           case XBoardGame:
9967             gn--;
9968             lastLoadGameStart = cm;
9969             break;
9970             
9971           case MoveNumberOne:
9972             switch (lastLoadGameStart) {
9973               case GNUChessGame:
9974               case XBoardGame:
9975               case PGNTag:
9976                 break;
9977               case MoveNumberOne:
9978               case (ChessMove) 0:
9979                 gn--;           /* count this game */
9980                 lastLoadGameStart = cm;
9981                 break;
9982               default:
9983                 /* impossible */
9984                 break;
9985             }
9986             break;
9987
9988           case PGNTag:
9989             switch (lastLoadGameStart) {
9990               case GNUChessGame:
9991               case PGNTag:
9992               case MoveNumberOne:
9993               case (ChessMove) 0:
9994                 gn--;           /* count this game */
9995                 lastLoadGameStart = cm;
9996                 break;
9997               case XBoardGame:
9998                 lastLoadGameStart = cm; /* game counted already */
9999                 break;
10000               default:
10001                 /* impossible */
10002                 break;
10003             }
10004             if (gn > 0) {
10005                 do {
10006                     yyboardindex = forwardMostMove;
10007                     cm = (ChessMove) yylex();
10008                 } while (cm == PGNTag || cm == Comment);
10009             }
10010             break;
10011
10012           case WhiteWins:
10013           case BlackWins:
10014           case GameIsDrawn:
10015             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10016                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10017                     != CMAIL_OLD_RESULT) {
10018                     nCmailResults ++ ;
10019                     cmailResult[  CMAIL_MAX_GAMES
10020                                 - gn - 1] = CMAIL_OLD_RESULT;
10021                 }
10022             }
10023             break;
10024
10025           case NormalMove:
10026             /* Only a NormalMove can be at the start of a game
10027              * without a position diagram. */
10028             if (lastLoadGameStart == (ChessMove) 0) {
10029               gn--;
10030               lastLoadGameStart = MoveNumberOne;
10031             }
10032             break;
10033
10034           default:
10035             break;
10036         }
10037     }
10038     
10039     if (appData.debugMode)
10040       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10041
10042     if (cm == XBoardGame) {
10043         /* Skip any header junk before position diagram and/or move 1 */
10044         for (;;) {
10045             yyboardindex = forwardMostMove;
10046             cm = (ChessMove) yylex();
10047
10048             if (cm == (ChessMove) 0 ||
10049                 cm == GNUChessGame || cm == XBoardGame) {
10050                 /* Empty game; pretend end-of-file and handle later */
10051                 cm = (ChessMove) 0;
10052                 break;
10053             }
10054
10055             if (cm == MoveNumberOne || cm == PositionDiagram ||
10056                 cm == PGNTag || cm == Comment)
10057               break;
10058         }
10059     } else if (cm == GNUChessGame) {
10060         if (gameInfo.event != NULL) {
10061             free(gameInfo.event);
10062         }
10063         gameInfo.event = StrSave(yy_text);
10064     }   
10065
10066     startedFromSetupPosition = FALSE;
10067     while (cm == PGNTag) {
10068         if (appData.debugMode) 
10069           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10070         err = ParsePGNTag(yy_text, &gameInfo);
10071         if (!err) numPGNTags++;
10072
10073         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10074         if(gameInfo.variant != oldVariant) {
10075             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10076             InitPosition(TRUE);
10077             oldVariant = gameInfo.variant;
10078             if (appData.debugMode) 
10079               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10080         }
10081
10082
10083         if (gameInfo.fen != NULL) {
10084           Board initial_position;
10085           startedFromSetupPosition = TRUE;
10086           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10087             Reset(TRUE, TRUE);
10088             DisplayError(_("Bad FEN position in file"), 0);
10089             return FALSE;
10090           }
10091           CopyBoard(boards[0], initial_position);
10092           if (blackPlaysFirst) {
10093             currentMove = forwardMostMove = backwardMostMove = 1;
10094             CopyBoard(boards[1], initial_position);
10095             strcpy(moveList[0], "");
10096             strcpy(parseList[0], "");
10097             timeRemaining[0][1] = whiteTimeRemaining;
10098             timeRemaining[1][1] = blackTimeRemaining;
10099             if (commentList[0] != NULL) {
10100               commentList[1] = commentList[0];
10101               commentList[0] = NULL;
10102             }
10103           } else {
10104             currentMove = forwardMostMove = backwardMostMove = 0;
10105           }
10106           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10107           {   int i;
10108               initialRulePlies = FENrulePlies;
10109               for( i=0; i< nrCastlingRights; i++ )
10110                   initialRights[i] = initial_position[CASTLING][i];
10111           }
10112           yyboardindex = forwardMostMove;
10113           free(gameInfo.fen);
10114           gameInfo.fen = NULL;
10115         }
10116
10117         yyboardindex = forwardMostMove;
10118         cm = (ChessMove) yylex();
10119
10120         /* Handle comments interspersed among the tags */
10121         while (cm == Comment) {
10122             char *p;
10123             if (appData.debugMode) 
10124               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10125             p = yy_text;
10126             AppendComment(currentMove, p, FALSE);
10127             yyboardindex = forwardMostMove;
10128             cm = (ChessMove) yylex();
10129         }
10130     }
10131
10132     /* don't rely on existence of Event tag since if game was
10133      * pasted from clipboard the Event tag may not exist
10134      */
10135     if (numPGNTags > 0){
10136         char *tags;
10137         if (gameInfo.variant == VariantNormal) {
10138           gameInfo.variant = StringToVariant(gameInfo.event);
10139         }
10140         if (!matchMode) {
10141           if( appData.autoDisplayTags ) {
10142             tags = PGNTags(&gameInfo);
10143             TagsPopUp(tags, CmailMsg());
10144             free(tags);
10145           }
10146         }
10147     } else {
10148         /* Make something up, but don't display it now */
10149         SetGameInfo();
10150         TagsPopDown();
10151     }
10152
10153     if (cm == PositionDiagram) {
10154         int i, j;
10155         char *p;
10156         Board initial_position;
10157
10158         if (appData.debugMode)
10159           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10160
10161         if (!startedFromSetupPosition) {
10162             p = yy_text;
10163             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10164               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10165                 switch (*p) {
10166                   case '[':
10167                   case '-':
10168                   case ' ':
10169                   case '\t':
10170                   case '\n':
10171                   case '\r':
10172                     break;
10173                   default:
10174                     initial_position[i][j++] = CharToPiece(*p);
10175                     break;
10176                 }
10177             while (*p == ' ' || *p == '\t' ||
10178                    *p == '\n' || *p == '\r') p++;
10179         
10180             if (strncmp(p, "black", strlen("black"))==0)
10181               blackPlaysFirst = TRUE;
10182             else
10183               blackPlaysFirst = FALSE;
10184             startedFromSetupPosition = TRUE;
10185         
10186             CopyBoard(boards[0], initial_position);
10187             if (blackPlaysFirst) {
10188                 currentMove = forwardMostMove = backwardMostMove = 1;
10189                 CopyBoard(boards[1], initial_position);
10190                 strcpy(moveList[0], "");
10191                 strcpy(parseList[0], "");
10192                 timeRemaining[0][1] = whiteTimeRemaining;
10193                 timeRemaining[1][1] = blackTimeRemaining;
10194                 if (commentList[0] != NULL) {
10195                     commentList[1] = commentList[0];
10196                     commentList[0] = NULL;
10197                 }
10198             } else {
10199                 currentMove = forwardMostMove = backwardMostMove = 0;
10200             }
10201         }
10202         yyboardindex = forwardMostMove;
10203         cm = (ChessMove) yylex();
10204     }
10205
10206     if (first.pr == NoProc) {
10207         StartChessProgram(&first);
10208     }
10209     InitChessProgram(&first, FALSE);
10210     SendToProgram("force\n", &first);
10211     if (startedFromSetupPosition) {
10212         SendBoard(&first, forwardMostMove);
10213     if (appData.debugMode) {
10214         fprintf(debugFP, "Load Game\n");
10215     }
10216         DisplayBothClocks();
10217     }      
10218
10219     /* [HGM] server: flag to write setup moves in broadcast file as one */
10220     loadFlag = appData.suppressLoadMoves;
10221
10222     while (cm == Comment) {
10223         char *p;
10224         if (appData.debugMode) 
10225           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10226         p = yy_text;
10227         AppendComment(currentMove, p, FALSE);
10228         yyboardindex = forwardMostMove;
10229         cm = (ChessMove) yylex();
10230     }
10231
10232     if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10233         cm == WhiteWins || cm == BlackWins ||
10234         cm == GameIsDrawn || cm == GameUnfinished) {
10235         DisplayMessage("", _("No moves in game"));
10236         if (cmailMsgLoaded) {
10237             if (appData.debugMode)
10238               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10239             ClearHighlights();
10240             flipView = FALSE;
10241         }
10242         DrawPosition(FALSE, boards[currentMove]);
10243         DisplayBothClocks();
10244         gameMode = EditGame;
10245         ModeHighlight();
10246         gameFileFP = NULL;
10247         cmailOldMove = 0;
10248         return TRUE;
10249     }
10250
10251     // [HGM] PV info: routine tests if comment empty
10252     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10253         DisplayComment(currentMove - 1, commentList[currentMove]);
10254     }
10255     if (!matchMode && appData.timeDelay != 0) 
10256       DrawPosition(FALSE, boards[currentMove]);
10257
10258     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10259       programStats.ok_to_send = 1;
10260     }
10261
10262     /* if the first token after the PGN tags is a move
10263      * and not move number 1, retrieve it from the parser 
10264      */
10265     if (cm != MoveNumberOne)
10266         LoadGameOneMove(cm);
10267
10268     /* load the remaining moves from the file */
10269     while (LoadGameOneMove((ChessMove)0)) {
10270       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10271       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10272     }
10273
10274     /* rewind to the start of the game */
10275     currentMove = backwardMostMove;
10276
10277     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10278
10279     if (oldGameMode == AnalyzeFile ||
10280         oldGameMode == AnalyzeMode) {
10281       AnalyzeFileEvent();
10282     }
10283
10284     if (matchMode || appData.timeDelay == 0) {
10285       ToEndEvent();
10286       gameMode = EditGame;
10287       ModeHighlight();
10288     } else if (appData.timeDelay > 0) {
10289       AutoPlayGameLoop();
10290     }
10291
10292     if (appData.debugMode) 
10293         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10294
10295     loadFlag = 0; /* [HGM] true game starts */
10296     return TRUE;
10297 }
10298
10299 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10300 int
10301 ReloadPosition(offset)
10302      int offset;
10303 {
10304     int positionNumber = lastLoadPositionNumber + offset;
10305     if (lastLoadPositionFP == NULL) {
10306         DisplayError(_("No position has been loaded yet"), 0);
10307         return FALSE;
10308     }
10309     if (positionNumber <= 0) {
10310         DisplayError(_("Can't back up any further"), 0);
10311         return FALSE;
10312     }
10313     return LoadPosition(lastLoadPositionFP, positionNumber,
10314                         lastLoadPositionTitle);
10315 }
10316
10317 /* Load the nth position from the given file */
10318 int
10319 LoadPositionFromFile(filename, n, title)
10320      char *filename;
10321      int n;
10322      char *title;
10323 {
10324     FILE *f;
10325     char buf[MSG_SIZ];
10326
10327     if (strcmp(filename, "-") == 0) {
10328         return LoadPosition(stdin, n, "stdin");
10329     } else {
10330         f = fopen(filename, "rb");
10331         if (f == NULL) {
10332             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10333             DisplayError(buf, errno);
10334             return FALSE;
10335         } else {
10336             return LoadPosition(f, n, title);
10337         }
10338     }
10339 }
10340
10341 /* Load the nth position from the given open file, and close it */
10342 int
10343 LoadPosition(f, positionNumber, title)
10344      FILE *f;
10345      int positionNumber;
10346      char *title;
10347 {
10348     char *p, line[MSG_SIZ];
10349     Board initial_position;
10350     int i, j, fenMode, pn;
10351     
10352     if (gameMode == Training )
10353         SetTrainingModeOff();
10354
10355     if (gameMode != BeginningOfGame) {
10356         Reset(FALSE, TRUE);
10357     }
10358     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10359         fclose(lastLoadPositionFP);
10360     }
10361     if (positionNumber == 0) positionNumber = 1;
10362     lastLoadPositionFP = f;
10363     lastLoadPositionNumber = positionNumber;
10364     strcpy(lastLoadPositionTitle, title);
10365     if (first.pr == NoProc) {
10366       StartChessProgram(&first);
10367       InitChessProgram(&first, FALSE);
10368     }    
10369     pn = positionNumber;
10370     if (positionNumber < 0) {
10371         /* Negative position number means to seek to that byte offset */
10372         if (fseek(f, -positionNumber, 0) == -1) {
10373             DisplayError(_("Can't seek on position file"), 0);
10374             return FALSE;
10375         };
10376         pn = 1;
10377     } else {
10378         if (fseek(f, 0, 0) == -1) {
10379             if (f == lastLoadPositionFP ?
10380                 positionNumber == lastLoadPositionNumber + 1 :
10381                 positionNumber == 1) {
10382                 pn = 1;
10383             } else {
10384                 DisplayError(_("Can't seek on position file"), 0);
10385                 return FALSE;
10386             }
10387         }
10388     }
10389     /* See if this file is FEN or old-style xboard */
10390     if (fgets(line, MSG_SIZ, f) == NULL) {
10391         DisplayError(_("Position not found in file"), 0);
10392         return FALSE;
10393     }
10394     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10395     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10396
10397     if (pn >= 2) {
10398         if (fenMode || line[0] == '#') pn--;
10399         while (pn > 0) {
10400             /* skip positions before number pn */
10401             if (fgets(line, MSG_SIZ, f) == NULL) {
10402                 Reset(TRUE, TRUE);
10403                 DisplayError(_("Position not found in file"), 0);
10404                 return FALSE;
10405             }
10406             if (fenMode || line[0] == '#') pn--;
10407         }
10408     }
10409
10410     if (fenMode) {
10411         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10412             DisplayError(_("Bad FEN position in file"), 0);
10413             return FALSE;
10414         }
10415     } else {
10416         (void) fgets(line, MSG_SIZ, f);
10417         (void) fgets(line, MSG_SIZ, f);
10418     
10419         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10420             (void) fgets(line, MSG_SIZ, f);
10421             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10422                 if (*p == ' ')
10423                   continue;
10424                 initial_position[i][j++] = CharToPiece(*p);
10425             }
10426         }
10427     
10428         blackPlaysFirst = FALSE;
10429         if (!feof(f)) {
10430             (void) fgets(line, MSG_SIZ, f);
10431             if (strncmp(line, "black", strlen("black"))==0)
10432               blackPlaysFirst = TRUE;
10433         }
10434     }
10435     startedFromSetupPosition = TRUE;
10436     
10437     SendToProgram("force\n", &first);
10438     CopyBoard(boards[0], initial_position);
10439     if (blackPlaysFirst) {
10440         currentMove = forwardMostMove = backwardMostMove = 1;
10441         strcpy(moveList[0], "");
10442         strcpy(parseList[0], "");
10443         CopyBoard(boards[1], initial_position);
10444         DisplayMessage("", _("Black to play"));
10445     } else {
10446         currentMove = forwardMostMove = backwardMostMove = 0;
10447         DisplayMessage("", _("White to play"));
10448     }
10449     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10450     SendBoard(&first, forwardMostMove);
10451     if (appData.debugMode) {
10452 int i, j;
10453   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10454   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10455         fprintf(debugFP, "Load Position\n");
10456     }
10457
10458     if (positionNumber > 1) {
10459         sprintf(line, "%s %d", title, positionNumber);
10460         DisplayTitle(line);
10461     } else {
10462         DisplayTitle(title);
10463     }
10464     gameMode = EditGame;
10465     ModeHighlight();
10466     ResetClocks();
10467     timeRemaining[0][1] = whiteTimeRemaining;
10468     timeRemaining[1][1] = blackTimeRemaining;
10469     DrawPosition(FALSE, boards[currentMove]);
10470    
10471     return TRUE;
10472 }
10473
10474
10475 void
10476 CopyPlayerNameIntoFileName(dest, src)
10477      char **dest, *src;
10478 {
10479     while (*src != NULLCHAR && *src != ',') {
10480         if (*src == ' ') {
10481             *(*dest)++ = '_';
10482             src++;
10483         } else {
10484             *(*dest)++ = *src++;
10485         }
10486     }
10487 }
10488
10489 char *DefaultFileName(ext)
10490      char *ext;
10491 {
10492     static char def[MSG_SIZ];
10493     char *p;
10494
10495     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10496         p = def;
10497         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10498         *p++ = '-';
10499         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10500         *p++ = '.';
10501         strcpy(p, ext);
10502     } else {
10503         def[0] = NULLCHAR;
10504     }
10505     return def;
10506 }
10507
10508 /* Save the current game to the given file */
10509 int
10510 SaveGameToFile(filename, append)
10511      char *filename;
10512      int append;
10513 {
10514     FILE *f;
10515     char buf[MSG_SIZ];
10516
10517     if (strcmp(filename, "-") == 0) {
10518         return SaveGame(stdout, 0, NULL);
10519     } else {
10520         f = fopen(filename, append ? "a" : "w");
10521         if (f == NULL) {
10522             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10523             DisplayError(buf, errno);
10524             return FALSE;
10525         } else {
10526             return SaveGame(f, 0, NULL);
10527         }
10528     }
10529 }
10530
10531 char *
10532 SavePart(str)
10533      char *str;
10534 {
10535     static char buf[MSG_SIZ];
10536     char *p;
10537     
10538     p = strchr(str, ' ');
10539     if (p == NULL) return str;
10540     strncpy(buf, str, p - str);
10541     buf[p - str] = NULLCHAR;
10542     return buf;
10543 }
10544
10545 #define PGN_MAX_LINE 75
10546
10547 #define PGN_SIDE_WHITE  0
10548 #define PGN_SIDE_BLACK  1
10549
10550 /* [AS] */
10551 static int FindFirstMoveOutOfBook( int side )
10552 {
10553     int result = -1;
10554
10555     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10556         int index = backwardMostMove;
10557         int has_book_hit = 0;
10558
10559         if( (index % 2) != side ) {
10560             index++;
10561         }
10562
10563         while( index < forwardMostMove ) {
10564             /* Check to see if engine is in book */
10565             int depth = pvInfoList[index].depth;
10566             int score = pvInfoList[index].score;
10567             int in_book = 0;
10568
10569             if( depth <= 2 ) {
10570                 in_book = 1;
10571             }
10572             else if( score == 0 && depth == 63 ) {
10573                 in_book = 1; /* Zappa */
10574             }
10575             else if( score == 2 && depth == 99 ) {
10576                 in_book = 1; /* Abrok */
10577             }
10578
10579             has_book_hit += in_book;
10580
10581             if( ! in_book ) {
10582                 result = index;
10583
10584                 break;
10585             }
10586
10587             index += 2;
10588         }
10589     }
10590
10591     return result;
10592 }
10593
10594 /* [AS] */
10595 void GetOutOfBookInfo( char * buf )
10596 {
10597     int oob[2];
10598     int i;
10599     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10600
10601     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10602     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10603
10604     *buf = '\0';
10605
10606     if( oob[0] >= 0 || oob[1] >= 0 ) {
10607         for( i=0; i<2; i++ ) {
10608             int idx = oob[i];
10609
10610             if( idx >= 0 ) {
10611                 if( i > 0 && oob[0] >= 0 ) {
10612                     strcat( buf, "   " );
10613                 }
10614
10615                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10616                 sprintf( buf+strlen(buf), "%s%.2f", 
10617                     pvInfoList[idx].score >= 0 ? "+" : "",
10618                     pvInfoList[idx].score / 100.0 );
10619             }
10620         }
10621     }
10622 }
10623
10624 /* Save game in PGN style and close the file */
10625 int
10626 SaveGamePGN(f)
10627      FILE *f;
10628 {
10629     int i, offset, linelen, newblock;
10630     time_t tm;
10631 //    char *movetext;
10632     char numtext[32];
10633     int movelen, numlen, blank;
10634     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10635
10636     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10637     
10638     tm = time((time_t *) NULL);
10639     
10640     PrintPGNTags(f, &gameInfo);
10641     
10642     if (backwardMostMove > 0 || startedFromSetupPosition) {
10643         char *fen = PositionToFEN(backwardMostMove, NULL);
10644         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10645         fprintf(f, "\n{--------------\n");
10646         PrintPosition(f, backwardMostMove);
10647         fprintf(f, "--------------}\n");
10648         free(fen);
10649     }
10650     else {
10651         /* [AS] Out of book annotation */
10652         if( appData.saveOutOfBookInfo ) {
10653             char buf[64];
10654
10655             GetOutOfBookInfo( buf );
10656
10657             if( buf[0] != '\0' ) {
10658                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); 
10659             }
10660         }
10661
10662         fprintf(f, "\n");
10663     }
10664
10665     i = backwardMostMove;
10666     linelen = 0;
10667     newblock = TRUE;
10668
10669     while (i < forwardMostMove) {
10670         /* Print comments preceding this move */
10671         if (commentList[i] != NULL) {
10672             if (linelen > 0) fprintf(f, "\n");
10673             fprintf(f, "%s", commentList[i]);
10674             linelen = 0;
10675             newblock = TRUE;
10676         }
10677
10678         /* Format move number */
10679         if ((i % 2) == 0) {
10680             sprintf(numtext, "%d.", (i - offset)/2 + 1);
10681         } else {
10682             if (newblock) {
10683                 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10684             } else {
10685                 numtext[0] = NULLCHAR;
10686             }
10687         }
10688         numlen = strlen(numtext);
10689         newblock = FALSE;
10690
10691         /* Print move number */
10692         blank = linelen > 0 && numlen > 0;
10693         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10694             fprintf(f, "\n");
10695             linelen = 0;
10696             blank = 0;
10697         }
10698         if (blank) {
10699             fprintf(f, " ");
10700             linelen++;
10701         }
10702         fprintf(f, "%s", numtext);
10703         linelen += numlen;
10704
10705         /* Get move */
10706         strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10707         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10708
10709         /* Print move */
10710         blank = linelen > 0 && movelen > 0;
10711         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10712             fprintf(f, "\n");
10713             linelen = 0;
10714             blank = 0;
10715         }
10716         if (blank) {
10717             fprintf(f, " ");
10718             linelen++;
10719         }
10720         fprintf(f, "%s", move_buffer);
10721         linelen += movelen;
10722
10723         /* [AS] Add PV info if present */
10724         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10725             /* [HGM] add time */
10726             char buf[MSG_SIZ]; int seconds;
10727
10728             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10729
10730             if( seconds <= 0) buf[0] = 0; else
10731             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10732                 seconds = (seconds + 4)/10; // round to full seconds
10733                 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10734                                    sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10735             }
10736
10737             sprintf( move_buffer, "{%s%.2f/%d%s}", 
10738                 pvInfoList[i].score >= 0 ? "+" : "",
10739                 pvInfoList[i].score / 100.0,
10740                 pvInfoList[i].depth,
10741                 buf );
10742
10743             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10744
10745             /* Print score/depth */
10746             blank = linelen > 0 && movelen > 0;
10747             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10748                 fprintf(f, "\n");
10749                 linelen = 0;
10750                 blank = 0;
10751             }
10752             if (blank) {
10753                 fprintf(f, " ");
10754                 linelen++;
10755             }
10756             fprintf(f, "%s", move_buffer);
10757             linelen += movelen;
10758         }
10759
10760         i++;
10761     }
10762     
10763     /* Start a new line */
10764     if (linelen > 0) fprintf(f, "\n");
10765
10766     /* Print comments after last move */
10767     if (commentList[i] != NULL) {
10768         fprintf(f, "%s\n", commentList[i]);
10769     }
10770
10771     /* Print result */
10772     if (gameInfo.resultDetails != NULL &&
10773         gameInfo.resultDetails[0] != NULLCHAR) {
10774         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10775                 PGNResult(gameInfo.result));
10776     } else {
10777         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10778     }
10779
10780     fclose(f);
10781     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10782     return TRUE;
10783 }
10784
10785 /* Save game in old style and close the file */
10786 int
10787 SaveGameOldStyle(f)
10788      FILE *f;
10789 {
10790     int i, offset;
10791     time_t tm;
10792     
10793     tm = time((time_t *) NULL);
10794     
10795     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10796     PrintOpponents(f);
10797     
10798     if (backwardMostMove > 0 || startedFromSetupPosition) {
10799         fprintf(f, "\n[--------------\n");
10800         PrintPosition(f, backwardMostMove);
10801         fprintf(f, "--------------]\n");
10802     } else {
10803         fprintf(f, "\n");
10804     }
10805
10806     i = backwardMostMove;
10807     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10808
10809     while (i < forwardMostMove) {
10810         if (commentList[i] != NULL) {
10811             fprintf(f, "[%s]\n", commentList[i]);
10812         }
10813
10814         if ((i % 2) == 1) {
10815             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10816             i++;
10817         } else {
10818             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10819             i++;
10820             if (commentList[i] != NULL) {
10821                 fprintf(f, "\n");
10822                 continue;
10823             }
10824             if (i >= forwardMostMove) {
10825                 fprintf(f, "\n");
10826                 break;
10827             }
10828             fprintf(f, "%s\n", parseList[i]);
10829             i++;
10830         }
10831     }
10832     
10833     if (commentList[i] != NULL) {
10834         fprintf(f, "[%s]\n", commentList[i]);
10835     }
10836
10837     /* This isn't really the old style, but it's close enough */
10838     if (gameInfo.resultDetails != NULL &&
10839         gameInfo.resultDetails[0] != NULLCHAR) {
10840         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10841                 gameInfo.resultDetails);
10842     } else {
10843         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10844     }
10845
10846     fclose(f);
10847     return TRUE;
10848 }
10849
10850 /* Save the current game to open file f and close the file */
10851 int
10852 SaveGame(f, dummy, dummy2)
10853      FILE *f;
10854      int dummy;
10855      char *dummy2;
10856 {
10857     if (gameMode == EditPosition) EditPositionDone(TRUE);
10858     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10859     if (appData.oldSaveStyle)
10860       return SaveGameOldStyle(f);
10861     else
10862       return SaveGamePGN(f);
10863 }
10864
10865 /* Save the current position to the given file */
10866 int
10867 SavePositionToFile(filename)
10868      char *filename;
10869 {
10870     FILE *f;
10871     char buf[MSG_SIZ];
10872
10873     if (strcmp(filename, "-") == 0) {
10874         return SavePosition(stdout, 0, NULL);
10875     } else {
10876         f = fopen(filename, "a");
10877         if (f == NULL) {
10878             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10879             DisplayError(buf, errno);
10880             return FALSE;
10881         } else {
10882             SavePosition(f, 0, NULL);
10883             return TRUE;
10884         }
10885     }
10886 }
10887
10888 /* Save the current position to the given open file and close the file */
10889 int
10890 SavePosition(f, dummy, dummy2)
10891      FILE *f;
10892      int dummy;
10893      char *dummy2;
10894 {
10895     time_t tm;
10896     char *fen;
10897     
10898     if (gameMode == EditPosition) EditPositionDone(TRUE);
10899     if (appData.oldSaveStyle) {
10900         tm = time((time_t *) NULL);
10901     
10902         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10903         PrintOpponents(f);
10904         fprintf(f, "[--------------\n");
10905         PrintPosition(f, currentMove);
10906         fprintf(f, "--------------]\n");
10907     } else {
10908         fen = PositionToFEN(currentMove, NULL);
10909         fprintf(f, "%s\n", fen);
10910         free(fen);
10911     }
10912     fclose(f);
10913     return TRUE;
10914 }
10915
10916 void
10917 ReloadCmailMsgEvent(unregister)
10918      int unregister;
10919 {
10920 #if !WIN32
10921     static char *inFilename = NULL;
10922     static char *outFilename;
10923     int i;
10924     struct stat inbuf, outbuf;
10925     int status;
10926     
10927     /* Any registered moves are unregistered if unregister is set, */
10928     /* i.e. invoked by the signal handler */
10929     if (unregister) {
10930         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10931             cmailMoveRegistered[i] = FALSE;
10932             if (cmailCommentList[i] != NULL) {
10933                 free(cmailCommentList[i]);
10934                 cmailCommentList[i] = NULL;
10935             }
10936         }
10937         nCmailMovesRegistered = 0;
10938     }
10939
10940     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10941         cmailResult[i] = CMAIL_NOT_RESULT;
10942     }
10943     nCmailResults = 0;
10944
10945     if (inFilename == NULL) {
10946         /* Because the filenames are static they only get malloced once  */
10947         /* and they never get freed                                      */
10948         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10949         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10950
10951         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10952         sprintf(outFilename, "%s.out", appData.cmailGameName);
10953     }
10954     
10955     status = stat(outFilename, &outbuf);
10956     if (status < 0) {
10957         cmailMailedMove = FALSE;
10958     } else {
10959         status = stat(inFilename, &inbuf);
10960         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10961     }
10962     
10963     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10964        counts the games, notes how each one terminated, etc.
10965        
10966        It would be nice to remove this kludge and instead gather all
10967        the information while building the game list.  (And to keep it
10968        in the game list nodes instead of having a bunch of fixed-size
10969        parallel arrays.)  Note this will require getting each game's
10970        termination from the PGN tags, as the game list builder does
10971        not process the game moves.  --mann
10972        */
10973     cmailMsgLoaded = TRUE;
10974     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10975     
10976     /* Load first game in the file or popup game menu */
10977     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10978
10979 #endif /* !WIN32 */
10980     return;
10981 }
10982
10983 int
10984 RegisterMove()
10985 {
10986     FILE *f;
10987     char string[MSG_SIZ];
10988
10989     if (   cmailMailedMove
10990         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10991         return TRUE;            /* Allow free viewing  */
10992     }
10993
10994     /* Unregister move to ensure that we don't leave RegisterMove        */
10995     /* with the move registered when the conditions for registering no   */
10996     /* longer hold                                                       */
10997     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10998         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10999         nCmailMovesRegistered --;
11000
11001         if (cmailCommentList[lastLoadGameNumber - 1] != NULL) 
11002           {
11003               free(cmailCommentList[lastLoadGameNumber - 1]);
11004               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11005           }
11006     }
11007
11008     if (cmailOldMove == -1) {
11009         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11010         return FALSE;
11011     }
11012
11013     if (currentMove > cmailOldMove + 1) {
11014         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11015         return FALSE;
11016     }
11017
11018     if (currentMove < cmailOldMove) {
11019         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11020         return FALSE;
11021     }
11022
11023     if (forwardMostMove > currentMove) {
11024         /* Silently truncate extra moves */
11025         TruncateGame();
11026     }
11027
11028     if (   (currentMove == cmailOldMove + 1)
11029         || (   (currentMove == cmailOldMove)
11030             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11031                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11032         if (gameInfo.result != GameUnfinished) {
11033             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11034         }
11035
11036         if (commentList[currentMove] != NULL) {
11037             cmailCommentList[lastLoadGameNumber - 1]
11038               = StrSave(commentList[currentMove]);
11039         }
11040         strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
11041
11042         if (appData.debugMode)
11043           fprintf(debugFP, "Saving %s for game %d\n",
11044                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11045
11046         sprintf(string,
11047                 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11048         
11049         f = fopen(string, "w");
11050         if (appData.oldSaveStyle) {
11051             SaveGameOldStyle(f); /* also closes the file */
11052             
11053             sprintf(string, "%s.pos.out", appData.cmailGameName);
11054             f = fopen(string, "w");
11055             SavePosition(f, 0, NULL); /* also closes the file */
11056         } else {
11057             fprintf(f, "{--------------\n");
11058             PrintPosition(f, currentMove);
11059             fprintf(f, "--------------}\n\n");
11060             
11061             SaveGame(f, 0, NULL); /* also closes the file*/
11062         }
11063         
11064         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11065         nCmailMovesRegistered ++;
11066     } else if (nCmailGames == 1) {
11067         DisplayError(_("You have not made a move yet"), 0);
11068         return FALSE;
11069     }
11070
11071     return TRUE;
11072 }
11073
11074 void
11075 MailMoveEvent()
11076 {
11077 #if !WIN32
11078     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11079     FILE *commandOutput;
11080     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11081     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11082     int nBuffers;
11083     int i;
11084     int archived;
11085     char *arcDir;
11086
11087     if (! cmailMsgLoaded) {
11088         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11089         return;
11090     }
11091
11092     if (nCmailGames == nCmailResults) {
11093         DisplayError(_("No unfinished games"), 0);
11094         return;
11095     }
11096
11097 #if CMAIL_PROHIBIT_REMAIL
11098     if (cmailMailedMove) {
11099         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);
11100         DisplayError(msg, 0);
11101         return;
11102     }
11103 #endif
11104
11105     if (! (cmailMailedMove || RegisterMove())) return;
11106     
11107     if (   cmailMailedMove
11108         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11109         sprintf(string, partCommandString,
11110                 appData.debugMode ? " -v" : "", appData.cmailGameName);
11111         commandOutput = popen(string, "r");
11112
11113         if (commandOutput == NULL) {
11114             DisplayError(_("Failed to invoke cmail"), 0);
11115         } else {
11116             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11117                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11118             }
11119             if (nBuffers > 1) {
11120                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11121                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11122                 nBytes = MSG_SIZ - 1;
11123             } else {
11124                 (void) memcpy(msg, buffer, nBytes);
11125             }
11126             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11127
11128             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11129                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11130
11131                 archived = TRUE;
11132                 for (i = 0; i < nCmailGames; i ++) {
11133                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11134                         archived = FALSE;
11135                     }
11136                 }
11137                 if (   archived
11138                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11139                         != NULL)) {
11140                     sprintf(buffer, "%s/%s.%s.archive",
11141                             arcDir,
11142                             appData.cmailGameName,
11143                             gameInfo.date);
11144                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11145                     cmailMsgLoaded = FALSE;
11146                 }
11147             }
11148
11149             DisplayInformation(msg);
11150             pclose(commandOutput);
11151         }
11152     } else {
11153         if ((*cmailMsg) != '\0') {
11154             DisplayInformation(cmailMsg);
11155         }
11156     }
11157
11158     return;
11159 #endif /* !WIN32 */
11160 }
11161
11162 char *
11163 CmailMsg()
11164 {
11165 #if WIN32
11166     return NULL;
11167 #else
11168     int  prependComma = 0;
11169     char number[5];
11170     char string[MSG_SIZ];       /* Space for game-list */
11171     int  i;
11172     
11173     if (!cmailMsgLoaded) return "";
11174
11175     if (cmailMailedMove) {
11176         sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
11177     } else {
11178         /* Create a list of games left */
11179         sprintf(string, "[");
11180         for (i = 0; i < nCmailGames; i ++) {
11181             if (! (   cmailMoveRegistered[i]
11182                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11183                 if (prependComma) {
11184                     sprintf(number, ",%d", i + 1);
11185                 } else {
11186                     sprintf(number, "%d", i + 1);
11187                     prependComma = 1;
11188                 }
11189                 
11190                 strcat(string, number);
11191             }
11192         }
11193         strcat(string, "]");
11194
11195         if (nCmailMovesRegistered + nCmailResults == 0) {
11196             switch (nCmailGames) {
11197               case 1:
11198                 sprintf(cmailMsg,
11199                         _("Still need to make move for game\n"));
11200                 break;
11201                 
11202               case 2:
11203                 sprintf(cmailMsg,
11204                         _("Still need to make moves for both games\n"));
11205                 break;
11206                 
11207               default:
11208                 sprintf(cmailMsg,
11209                         _("Still need to make moves for all %d games\n"),
11210                         nCmailGames);
11211                 break;
11212             }
11213         } else {
11214             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11215               case 1:
11216                 sprintf(cmailMsg,
11217                         _("Still need to make a move for game %s\n"),
11218                         string);
11219                 break;
11220                 
11221               case 0:
11222                 if (nCmailResults == nCmailGames) {
11223                     sprintf(cmailMsg, _("No unfinished games\n"));
11224                 } else {
11225                     sprintf(cmailMsg, _("Ready to send mail\n"));
11226                 }
11227                 break;
11228                 
11229               default:
11230                 sprintf(cmailMsg,
11231                         _("Still need to make moves for games %s\n"),
11232                         string);
11233             }
11234         }
11235     }
11236     return cmailMsg;
11237 #endif /* WIN32 */
11238 }
11239
11240 void
11241 ResetGameEvent()
11242 {
11243     if (gameMode == Training)
11244       SetTrainingModeOff();
11245
11246     Reset(TRUE, TRUE);
11247     cmailMsgLoaded = FALSE;
11248     if (appData.icsActive) {
11249       SendToICS(ics_prefix);
11250       SendToICS("refresh\n");
11251     }
11252 }
11253
11254 void
11255 ExitEvent(status)
11256      int status;
11257 {
11258     exiting++;
11259     if (exiting > 2) {
11260       /* Give up on clean exit */
11261       exit(status);
11262     }
11263     if (exiting > 1) {
11264       /* Keep trying for clean exit */
11265       return;
11266     }
11267
11268     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11269
11270     if (telnetISR != NULL) {
11271       RemoveInputSource(telnetISR);
11272     }
11273     if (icsPR != NoProc) {
11274       DestroyChildProcess(icsPR, TRUE);
11275     }
11276
11277     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11278     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11279
11280     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11281     /* make sure this other one finishes before killing it!                  */
11282     if(endingGame) { int count = 0;
11283         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11284         while(endingGame && count++ < 10) DoSleep(1);
11285         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11286     }
11287
11288     /* Kill off chess programs */
11289     if (first.pr != NoProc) {
11290         ExitAnalyzeMode();
11291         
11292         DoSleep( appData.delayBeforeQuit );
11293         SendToProgram("quit\n", &first);
11294         DoSleep( appData.delayAfterQuit );
11295         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11296     }
11297     if (second.pr != NoProc) {
11298         DoSleep( appData.delayBeforeQuit );
11299         SendToProgram("quit\n", &second);
11300         DoSleep( appData.delayAfterQuit );
11301         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11302     }
11303     if (first.isr != NULL) {
11304         RemoveInputSource(first.isr);
11305     }
11306     if (second.isr != NULL) {
11307         RemoveInputSource(second.isr);
11308     }
11309
11310     ShutDownFrontEnd();
11311     exit(status);
11312 }
11313
11314 void
11315 PauseEvent()
11316 {
11317     if (appData.debugMode)
11318         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11319     if (pausing) {
11320         pausing = FALSE;
11321         ModeHighlight();
11322         if (gameMode == MachinePlaysWhite ||
11323             gameMode == MachinePlaysBlack) {
11324             StartClocks();
11325         } else {
11326             DisplayBothClocks();
11327         }
11328         if (gameMode == PlayFromGameFile) {
11329             if (appData.timeDelay >= 0) 
11330                 AutoPlayGameLoop();
11331         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11332             Reset(FALSE, TRUE);
11333             SendToICS(ics_prefix);
11334             SendToICS("refresh\n");
11335         } else if (currentMove < forwardMostMove) {
11336             ForwardInner(forwardMostMove);
11337         }
11338         pauseExamInvalid = FALSE;
11339     } else {
11340         switch (gameMode) {
11341           default:
11342             return;
11343           case IcsExamining:
11344             pauseExamForwardMostMove = forwardMostMove;
11345             pauseExamInvalid = FALSE;
11346             /* fall through */
11347           case IcsObserving:
11348           case IcsPlayingWhite:
11349           case IcsPlayingBlack:
11350             pausing = TRUE;
11351             ModeHighlight();
11352             return;
11353           case PlayFromGameFile:
11354             (void) StopLoadGameTimer();
11355             pausing = TRUE;
11356             ModeHighlight();
11357             break;
11358           case BeginningOfGame:
11359             if (appData.icsActive) return;
11360             /* else fall through */
11361           case MachinePlaysWhite:
11362           case MachinePlaysBlack:
11363           case TwoMachinesPlay:
11364             if (forwardMostMove == 0)
11365               return;           /* don't pause if no one has moved */
11366             if ((gameMode == MachinePlaysWhite &&
11367                  !WhiteOnMove(forwardMostMove)) ||
11368                 (gameMode == MachinePlaysBlack &&
11369                  WhiteOnMove(forwardMostMove))) {
11370                 StopClocks();
11371             }
11372             pausing = TRUE;
11373             ModeHighlight();
11374             break;
11375         }
11376     }
11377 }
11378
11379 void
11380 EditCommentEvent()
11381 {
11382     char title[MSG_SIZ];
11383
11384     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11385         strcpy(title, _("Edit comment"));
11386     } else {
11387         sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11388                 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11389                 parseList[currentMove - 1]);
11390     }
11391
11392     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11393 }
11394
11395
11396 void
11397 EditTagsEvent()
11398 {
11399     char *tags = PGNTags(&gameInfo);
11400     EditTagsPopUp(tags);
11401     free(tags);
11402 }
11403
11404 void
11405 AnalyzeModeEvent()
11406 {
11407     if (appData.noChessProgram || gameMode == AnalyzeMode)
11408       return;
11409
11410     if (gameMode != AnalyzeFile) {
11411         if (!appData.icsEngineAnalyze) {
11412                EditGameEvent();
11413                if (gameMode != EditGame) return;
11414         }
11415         ResurrectChessProgram();
11416         SendToProgram("analyze\n", &first);
11417         first.analyzing = TRUE;
11418         /*first.maybeThinking = TRUE;*/
11419         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11420         EngineOutputPopUp();
11421     }
11422     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11423     pausing = FALSE;
11424     ModeHighlight();
11425     SetGameInfo();
11426
11427     StartAnalysisClock();
11428     GetTimeMark(&lastNodeCountTime);
11429     lastNodeCount = 0;
11430 }
11431
11432 void
11433 AnalyzeFileEvent()
11434 {
11435     if (appData.noChessProgram || gameMode == AnalyzeFile)
11436       return;
11437
11438     if (gameMode != AnalyzeMode) {
11439         EditGameEvent();
11440         if (gameMode != EditGame) return;
11441         ResurrectChessProgram();
11442         SendToProgram("analyze\n", &first);
11443         first.analyzing = TRUE;
11444         /*first.maybeThinking = TRUE;*/
11445         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11446         EngineOutputPopUp();
11447     }
11448     gameMode = AnalyzeFile;
11449     pausing = FALSE;
11450     ModeHighlight();
11451     SetGameInfo();
11452
11453     StartAnalysisClock();
11454     GetTimeMark(&lastNodeCountTime);
11455     lastNodeCount = 0;
11456 }
11457
11458 void
11459 MachineWhiteEvent()
11460 {
11461     char buf[MSG_SIZ];
11462     char *bookHit = NULL;
11463
11464     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11465       return;
11466
11467
11468     if (gameMode == PlayFromGameFile || 
11469         gameMode == TwoMachinesPlay  || 
11470         gameMode == Training         || 
11471         gameMode == AnalyzeMode      || 
11472         gameMode == EndOfGame)
11473         EditGameEvent();
11474
11475     if (gameMode == EditPosition) 
11476         EditPositionDone(TRUE);
11477
11478     if (!WhiteOnMove(currentMove)) {
11479         DisplayError(_("It is not White's turn"), 0);
11480         return;
11481     }
11482   
11483     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11484       ExitAnalyzeMode();
11485
11486     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11487         gameMode == AnalyzeFile)
11488         TruncateGame();
11489
11490     ResurrectChessProgram();    /* in case it isn't running */
11491     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11492         gameMode = MachinePlaysWhite;
11493         ResetClocks();
11494     } else
11495     gameMode = MachinePlaysWhite;
11496     pausing = FALSE;
11497     ModeHighlight();
11498     SetGameInfo();
11499     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11500     DisplayTitle(buf);
11501     if (first.sendName) {
11502       sprintf(buf, "name %s\n", gameInfo.black);
11503       SendToProgram(buf, &first);
11504     }
11505     if (first.sendTime) {
11506       if (first.useColors) {
11507         SendToProgram("black\n", &first); /*gnu kludge*/
11508       }
11509       SendTimeRemaining(&first, TRUE);
11510     }
11511     if (first.useColors) {
11512       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11513     }
11514     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11515     SetMachineThinkingEnables();
11516     first.maybeThinking = TRUE;
11517     StartClocks();
11518     firstMove = FALSE;
11519
11520     if (appData.autoFlipView && !flipView) {
11521       flipView = !flipView;
11522       DrawPosition(FALSE, NULL);
11523       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11524     }
11525
11526     if(bookHit) { // [HGM] book: simulate book reply
11527         static char bookMove[MSG_SIZ]; // a bit generous?
11528
11529         programStats.nodes = programStats.depth = programStats.time = 
11530         programStats.score = programStats.got_only_move = 0;
11531         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11532
11533         strcpy(bookMove, "move ");
11534         strcat(bookMove, bookHit);
11535         HandleMachineMove(bookMove, &first);
11536     }
11537 }
11538
11539 void
11540 MachineBlackEvent()
11541 {
11542     char buf[MSG_SIZ];
11543    char *bookHit = NULL;
11544
11545     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11546         return;
11547
11548
11549     if (gameMode == PlayFromGameFile || 
11550         gameMode == TwoMachinesPlay  || 
11551         gameMode == Training         || 
11552         gameMode == AnalyzeMode      || 
11553         gameMode == EndOfGame)
11554         EditGameEvent();
11555
11556     if (gameMode == EditPosition) 
11557         EditPositionDone(TRUE);
11558
11559     if (WhiteOnMove(currentMove)) {
11560         DisplayError(_("It is not Black's turn"), 0);
11561         return;
11562     }
11563     
11564     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11565       ExitAnalyzeMode();
11566
11567     if (gameMode == EditGame || gameMode == AnalyzeMode || 
11568         gameMode == AnalyzeFile)
11569         TruncateGame();
11570
11571     ResurrectChessProgram();    /* in case it isn't running */
11572     gameMode = MachinePlaysBlack;
11573     pausing = FALSE;
11574     ModeHighlight();
11575     SetGameInfo();
11576     sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11577     DisplayTitle(buf);
11578     if (first.sendName) {
11579       sprintf(buf, "name %s\n", gameInfo.white);
11580       SendToProgram(buf, &first);
11581     }
11582     if (first.sendTime) {
11583       if (first.useColors) {
11584         SendToProgram("white\n", &first); /*gnu kludge*/
11585       }
11586       SendTimeRemaining(&first, FALSE);
11587     }
11588     if (first.useColors) {
11589       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11590     }
11591     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11592     SetMachineThinkingEnables();
11593     first.maybeThinking = TRUE;
11594     StartClocks();
11595
11596     if (appData.autoFlipView && flipView) {
11597       flipView = !flipView;
11598       DrawPosition(FALSE, NULL);
11599       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11600     }
11601     if(bookHit) { // [HGM] book: simulate book reply
11602         static char bookMove[MSG_SIZ]; // a bit generous?
11603
11604         programStats.nodes = programStats.depth = programStats.time = 
11605         programStats.score = programStats.got_only_move = 0;
11606         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11607
11608         strcpy(bookMove, "move ");
11609         strcat(bookMove, bookHit);
11610         HandleMachineMove(bookMove, &first);
11611     }
11612 }
11613
11614
11615 void
11616 DisplayTwoMachinesTitle()
11617 {
11618     char buf[MSG_SIZ];
11619     if (appData.matchGames > 0) {
11620         if (first.twoMachinesColor[0] == 'w') {
11621             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11622                     gameInfo.white, gameInfo.black,
11623                     first.matchWins, second.matchWins,
11624                     matchGame - 1 - (first.matchWins + second.matchWins));
11625         } else {
11626             sprintf(buf, "%s vs. %s (%d-%d-%d)",
11627                     gameInfo.white, gameInfo.black,
11628                     second.matchWins, first.matchWins,
11629                     matchGame - 1 - (first.matchWins + second.matchWins));
11630         }
11631     } else {
11632         sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11633     }
11634     DisplayTitle(buf);
11635 }
11636
11637 void
11638 TwoMachinesEvent P((void))
11639 {
11640     int i;
11641     char buf[MSG_SIZ];
11642     ChessProgramState *onmove;
11643     char *bookHit = NULL;
11644     
11645     if (appData.noChessProgram) return;
11646
11647     switch (gameMode) {
11648       case TwoMachinesPlay:
11649         return;
11650       case MachinePlaysWhite:
11651       case MachinePlaysBlack:
11652         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11653             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11654             return;
11655         }
11656         /* fall through */
11657       case BeginningOfGame:
11658       case PlayFromGameFile:
11659       case EndOfGame:
11660         EditGameEvent();
11661         if (gameMode != EditGame) return;
11662         break;
11663       case EditPosition:
11664         EditPositionDone(TRUE);
11665         break;
11666       case AnalyzeMode:
11667       case AnalyzeFile:
11668         ExitAnalyzeMode();
11669         break;
11670       case EditGame:
11671       default:
11672         break;
11673     }
11674
11675 //    forwardMostMove = currentMove;
11676     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11677     ResurrectChessProgram();    /* in case first program isn't running */
11678
11679     if (second.pr == NULL) {
11680         StartChessProgram(&second);
11681         if (second.protocolVersion == 1) {
11682           TwoMachinesEventIfReady();
11683         } else {
11684           /* kludge: allow timeout for initial "feature" command */
11685           FreezeUI();
11686           DisplayMessage("", _("Starting second chess program"));
11687           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11688         }
11689         return;
11690     }
11691     DisplayMessage("", "");
11692     InitChessProgram(&second, FALSE);
11693     SendToProgram("force\n", &second);
11694     if (startedFromSetupPosition) {
11695         SendBoard(&second, backwardMostMove);
11696     if (appData.debugMode) {
11697         fprintf(debugFP, "Two Machines\n");
11698     }
11699     }
11700     for (i = backwardMostMove; i < forwardMostMove; i++) {
11701         SendMoveToProgram(i, &second);
11702     }
11703
11704     gameMode = TwoMachinesPlay;
11705     pausing = FALSE;
11706     ModeHighlight();
11707     SetGameInfo();
11708     DisplayTwoMachinesTitle();
11709     firstMove = TRUE;
11710     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11711         onmove = &first;
11712     } else {
11713         onmove = &second;
11714     }
11715
11716     SendToProgram(first.computerString, &first);
11717     if (first.sendName) {
11718       sprintf(buf, "name %s\n", second.tidy);
11719       SendToProgram(buf, &first);
11720     }
11721     SendToProgram(second.computerString, &second);
11722     if (second.sendName) {
11723       sprintf(buf, "name %s\n", first.tidy);
11724       SendToProgram(buf, &second);
11725     }
11726
11727     ResetClocks();
11728     if (!first.sendTime || !second.sendTime) {
11729         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11730         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11731     }
11732     if (onmove->sendTime) {
11733       if (onmove->useColors) {
11734         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11735       }
11736       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11737     }
11738     if (onmove->useColors) {
11739       SendToProgram(onmove->twoMachinesColor, onmove);
11740     }
11741     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11742 //    SendToProgram("go\n", onmove);
11743     onmove->maybeThinking = TRUE;
11744     SetMachineThinkingEnables();
11745
11746     StartClocks();
11747
11748     if(bookHit) { // [HGM] book: simulate book reply
11749         static char bookMove[MSG_SIZ]; // a bit generous?
11750
11751         programStats.nodes = programStats.depth = programStats.time = 
11752         programStats.score = programStats.got_only_move = 0;
11753         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11754
11755         strcpy(bookMove, "move ");
11756         strcat(bookMove, bookHit);
11757         savedMessage = bookMove; // args for deferred call
11758         savedState = onmove;
11759         ScheduleDelayedEvent(DeferredBookMove, 1);
11760     }
11761 }
11762
11763 void
11764 TrainingEvent()
11765 {
11766     if (gameMode == Training) {
11767       SetTrainingModeOff();
11768       gameMode = PlayFromGameFile;
11769       DisplayMessage("", _("Training mode off"));
11770     } else {
11771       gameMode = Training;
11772       animateTraining = appData.animate;
11773
11774       /* make sure we are not already at the end of the game */
11775       if (currentMove < forwardMostMove) {
11776         SetTrainingModeOn();
11777         DisplayMessage("", _("Training mode on"));
11778       } else {
11779         gameMode = PlayFromGameFile;
11780         DisplayError(_("Already at end of game"), 0);
11781       }
11782     }
11783     ModeHighlight();
11784 }
11785
11786 void
11787 IcsClientEvent()
11788 {
11789     if (!appData.icsActive) return;
11790     switch (gameMode) {
11791       case IcsPlayingWhite:
11792       case IcsPlayingBlack:
11793       case IcsObserving:
11794       case IcsIdle:
11795       case BeginningOfGame:
11796       case IcsExamining:
11797         return;
11798
11799       case EditGame:
11800         break;
11801
11802       case EditPosition:
11803         EditPositionDone(TRUE);
11804         break;
11805
11806       case AnalyzeMode:
11807       case AnalyzeFile:
11808         ExitAnalyzeMode();
11809         break;
11810         
11811       default:
11812         EditGameEvent();
11813         break;
11814     }
11815
11816     gameMode = IcsIdle;
11817     ModeHighlight();
11818     return;
11819 }
11820
11821
11822 void
11823 EditGameEvent()
11824 {
11825     int i;
11826
11827     switch (gameMode) {
11828       case Training:
11829         SetTrainingModeOff();
11830         break;
11831       case MachinePlaysWhite:
11832       case MachinePlaysBlack:
11833       case BeginningOfGame:
11834         SendToProgram("force\n", &first);
11835         SetUserThinkingEnables();
11836         break;
11837       case PlayFromGameFile:
11838         (void) StopLoadGameTimer();
11839         if (gameFileFP != NULL) {
11840             gameFileFP = NULL;
11841         }
11842         break;
11843       case EditPosition:
11844         EditPositionDone(TRUE);
11845         break;
11846       case AnalyzeMode:
11847       case AnalyzeFile:
11848         ExitAnalyzeMode();
11849         SendToProgram("force\n", &first);
11850         break;
11851       case TwoMachinesPlay:
11852         GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11853         ResurrectChessProgram();
11854         SetUserThinkingEnables();
11855         break;
11856       case EndOfGame:
11857         ResurrectChessProgram();
11858         break;
11859       case IcsPlayingBlack:
11860       case IcsPlayingWhite:
11861         DisplayError(_("Warning: You are still playing a game"), 0);
11862         break;
11863       case IcsObserving:
11864         DisplayError(_("Warning: You are still observing a game"), 0);
11865         break;
11866       case IcsExamining:
11867         DisplayError(_("Warning: You are still examining a game"), 0);
11868         break;
11869       case IcsIdle:
11870         break;
11871       case EditGame:
11872       default:
11873         return;
11874     }
11875     
11876     pausing = FALSE;
11877     StopClocks();
11878     first.offeredDraw = second.offeredDraw = 0;
11879
11880     if (gameMode == PlayFromGameFile) {
11881         whiteTimeRemaining = timeRemaining[0][currentMove];
11882         blackTimeRemaining = timeRemaining[1][currentMove];
11883         DisplayTitle("");
11884     }
11885
11886     if (gameMode == MachinePlaysWhite ||
11887         gameMode == MachinePlaysBlack ||
11888         gameMode == TwoMachinesPlay ||
11889         gameMode == EndOfGame) {
11890         i = forwardMostMove;
11891         while (i > currentMove) {
11892             SendToProgram("undo\n", &first);
11893             i--;
11894         }
11895         whiteTimeRemaining = timeRemaining[0][currentMove];
11896         blackTimeRemaining = timeRemaining[1][currentMove];
11897         DisplayBothClocks();
11898         if (whiteFlag || blackFlag) {
11899             whiteFlag = blackFlag = 0;
11900         }
11901         DisplayTitle("");
11902     }           
11903     
11904     gameMode = EditGame;
11905     ModeHighlight();
11906     SetGameInfo();
11907 }
11908
11909
11910 void
11911 EditPositionEvent()
11912 {
11913     if (gameMode == EditPosition) {
11914         EditGameEvent();
11915         return;
11916     }
11917     
11918     EditGameEvent();
11919     if (gameMode != EditGame) return;
11920     
11921     gameMode = EditPosition;
11922     ModeHighlight();
11923     SetGameInfo();
11924     if (currentMove > 0)
11925       CopyBoard(boards[0], boards[currentMove]);
11926     
11927     blackPlaysFirst = !WhiteOnMove(currentMove);
11928     ResetClocks();
11929     currentMove = forwardMostMove = backwardMostMove = 0;
11930     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11931     DisplayMove(-1);
11932 }
11933
11934 void
11935 ExitAnalyzeMode()
11936 {
11937     /* [DM] icsEngineAnalyze - possible call from other functions */
11938     if (appData.icsEngineAnalyze) {
11939         appData.icsEngineAnalyze = FALSE;
11940
11941         DisplayMessage("",_("Close ICS engine analyze..."));
11942     }
11943     if (first.analysisSupport && first.analyzing) {
11944       SendToProgram("exit\n", &first);
11945       first.analyzing = FALSE;
11946     }
11947     thinkOutput[0] = NULLCHAR;
11948 }
11949
11950 void
11951 EditPositionDone(Boolean fakeRights)
11952 {
11953     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11954
11955     startedFromSetupPosition = TRUE;
11956     InitChessProgram(&first, FALSE);
11957     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11958       boards[0][EP_STATUS] = EP_NONE;
11959       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11960     if(boards[0][0][BOARD_WIDTH>>1] == king) {
11961         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11962         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11963       } else boards[0][CASTLING][2] = NoRights;
11964     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11965         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11966         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11967       } else boards[0][CASTLING][5] = NoRights;
11968     }
11969     SendToProgram("force\n", &first);
11970     if (blackPlaysFirst) {
11971         strcpy(moveList[0], "");
11972         strcpy(parseList[0], "");
11973         currentMove = forwardMostMove = backwardMostMove = 1;
11974         CopyBoard(boards[1], boards[0]);
11975     } else {
11976         currentMove = forwardMostMove = backwardMostMove = 0;
11977     }
11978     SendBoard(&first, forwardMostMove);
11979     if (appData.debugMode) {
11980         fprintf(debugFP, "EditPosDone\n");
11981     }
11982     DisplayTitle("");
11983     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11984     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11985     gameMode = EditGame;
11986     ModeHighlight();
11987     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11988     ClearHighlights(); /* [AS] */
11989 }
11990
11991 /* Pause for `ms' milliseconds */
11992 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11993 void
11994 TimeDelay(ms)
11995      long ms;
11996 {
11997     TimeMark m1, m2;
11998
11999     GetTimeMark(&m1);
12000     do {
12001         GetTimeMark(&m2);
12002     } while (SubtractTimeMarks(&m2, &m1) < ms);
12003 }
12004
12005 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12006 void
12007 SendMultiLineToICS(buf)
12008      char *buf;
12009 {
12010     char temp[MSG_SIZ+1], *p;
12011     int len;
12012
12013     len = strlen(buf);
12014     if (len > MSG_SIZ)
12015       len = MSG_SIZ;
12016   
12017     strncpy(temp, buf, len);
12018     temp[len] = 0;
12019
12020     p = temp;
12021     while (*p) {
12022         if (*p == '\n' || *p == '\r')
12023           *p = ' ';
12024         ++p;
12025     }
12026
12027     strcat(temp, "\n");
12028     SendToICS(temp);
12029     SendToPlayer(temp, strlen(temp));
12030 }
12031
12032 void
12033 SetWhiteToPlayEvent()
12034 {
12035     if (gameMode == EditPosition) {
12036         blackPlaysFirst = FALSE;
12037         DisplayBothClocks();    /* works because currentMove is 0 */
12038     } else if (gameMode == IcsExamining) {
12039         SendToICS(ics_prefix);
12040         SendToICS("tomove white\n");
12041     }
12042 }
12043
12044 void
12045 SetBlackToPlayEvent()
12046 {
12047     if (gameMode == EditPosition) {
12048         blackPlaysFirst = TRUE;
12049         currentMove = 1;        /* kludge */
12050         DisplayBothClocks();
12051         currentMove = 0;
12052     } else if (gameMode == IcsExamining) {
12053         SendToICS(ics_prefix);
12054         SendToICS("tomove black\n");
12055     }
12056 }
12057
12058 void
12059 EditPositionMenuEvent(selection, x, y)
12060      ChessSquare selection;
12061      int x, y;
12062 {
12063     char buf[MSG_SIZ];
12064     ChessSquare piece = boards[0][y][x];
12065
12066     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12067
12068     switch (selection) {
12069       case ClearBoard:
12070         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12071             SendToICS(ics_prefix);
12072             SendToICS("bsetup clear\n");
12073         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12074             SendToICS(ics_prefix);
12075             SendToICS("clearboard\n");
12076         } else {
12077             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12078                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12079                 for (y = 0; y < BOARD_HEIGHT; y++) {
12080                     if (gameMode == IcsExamining) {
12081                         if (boards[currentMove][y][x] != EmptySquare) {
12082                             sprintf(buf, "%sx@%c%c\n", ics_prefix,
12083                                     AAA + x, ONE + y);
12084                             SendToICS(buf);
12085                         }
12086                     } else {
12087                         boards[0][y][x] = p;
12088                     }
12089                 }
12090             }
12091         }
12092         if (gameMode == EditPosition) {
12093             DrawPosition(FALSE, boards[0]);
12094         }
12095         break;
12096
12097       case WhitePlay:
12098         SetWhiteToPlayEvent();
12099         break;
12100
12101       case BlackPlay:
12102         SetBlackToPlayEvent();
12103         break;
12104
12105       case EmptySquare:
12106         if (gameMode == IcsExamining) {
12107             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12108             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12109             SendToICS(buf);
12110         } else {
12111             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12112                 if(x == BOARD_LEFT-2) {
12113                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12114                     boards[0][y][1] = 0;
12115                 } else
12116                 if(x == BOARD_RGHT+1) {
12117                     if(y >= gameInfo.holdingsSize) break;
12118                     boards[0][y][BOARD_WIDTH-2] = 0;
12119                 } else break;
12120             }
12121             boards[0][y][x] = EmptySquare;
12122             DrawPosition(FALSE, boards[0]);
12123         }
12124         break;
12125
12126       case PromotePiece:
12127         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12128            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12129             selection = (ChessSquare) (PROMOTED piece);
12130         } else if(piece == EmptySquare) selection = WhiteSilver;
12131         else selection = (ChessSquare)((int)piece - 1);
12132         goto defaultlabel;
12133
12134       case DemotePiece:
12135         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12136            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12137             selection = (ChessSquare) (DEMOTED piece);
12138         } else if(piece == EmptySquare) selection = BlackSilver;
12139         else selection = (ChessSquare)((int)piece + 1);       
12140         goto defaultlabel;
12141
12142       case WhiteQueen:
12143       case BlackQueen:
12144         if(gameInfo.variant == VariantShatranj ||
12145            gameInfo.variant == VariantXiangqi  ||
12146            gameInfo.variant == VariantCourier  ||
12147            gameInfo.variant == VariantMakruk     )
12148             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12149         goto defaultlabel;
12150
12151       case WhiteKing:
12152       case BlackKing:
12153         if(gameInfo.variant == VariantXiangqi)
12154             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12155         if(gameInfo.variant == VariantKnightmate)
12156             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12157       default:
12158         defaultlabel:
12159         if (gameMode == IcsExamining) {
12160             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12161             sprintf(buf, "%s%c@%c%c\n", ics_prefix,
12162                     PieceToChar(selection), AAA + x, ONE + y);
12163             SendToICS(buf);
12164         } else {
12165             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12166                 int n;
12167                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12168                     n = PieceToNumber(selection - BlackPawn);
12169                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12170                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12171                     boards[0][BOARD_HEIGHT-1-n][1]++;
12172                 } else
12173                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12174                     n = PieceToNumber(selection);
12175                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12176                     boards[0][n][BOARD_WIDTH-1] = selection;
12177                     boards[0][n][BOARD_WIDTH-2]++;
12178                 }
12179             } else
12180             boards[0][y][x] = selection;
12181             DrawPosition(TRUE, boards[0]);
12182         }
12183         break;
12184     }
12185 }
12186
12187
12188 void
12189 DropMenuEvent(selection, x, y)
12190      ChessSquare selection;
12191      int x, y;
12192 {
12193     ChessMove moveType;
12194
12195     switch (gameMode) {
12196       case IcsPlayingWhite:
12197       case MachinePlaysBlack:
12198         if (!WhiteOnMove(currentMove)) {
12199             DisplayMoveError(_("It is Black's turn"));
12200             return;
12201         }
12202         moveType = WhiteDrop;
12203         break;
12204       case IcsPlayingBlack:
12205       case MachinePlaysWhite:
12206         if (WhiteOnMove(currentMove)) {
12207             DisplayMoveError(_("It is White's turn"));
12208             return;
12209         }
12210         moveType = BlackDrop;
12211         break;
12212       case EditGame:
12213         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12214         break;
12215       default:
12216         return;
12217     }
12218
12219     if (moveType == BlackDrop && selection < BlackPawn) {
12220       selection = (ChessSquare) ((int) selection
12221                                  + (int) BlackPawn - (int) WhitePawn);
12222     }
12223     if (boards[currentMove][y][x] != EmptySquare) {
12224         DisplayMoveError(_("That square is occupied"));
12225         return;
12226     }
12227
12228     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12229 }
12230
12231 void
12232 AcceptEvent()
12233 {
12234     /* Accept a pending offer of any kind from opponent */
12235     
12236     if (appData.icsActive) {
12237         SendToICS(ics_prefix);
12238         SendToICS("accept\n");
12239     } else if (cmailMsgLoaded) {
12240         if (currentMove == cmailOldMove &&
12241             commentList[cmailOldMove] != NULL &&
12242             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12243                    "Black offers a draw" : "White offers a draw")) {
12244             TruncateGame();
12245             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12246             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12247         } else {
12248             DisplayError(_("There is no pending offer on this move"), 0);
12249             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12250         }
12251     } else {
12252         /* Not used for offers from chess program */
12253     }
12254 }
12255
12256 void
12257 DeclineEvent()
12258 {
12259     /* Decline a pending offer of any kind from opponent */
12260     
12261     if (appData.icsActive) {
12262         SendToICS(ics_prefix);
12263         SendToICS("decline\n");
12264     } else if (cmailMsgLoaded) {
12265         if (currentMove == cmailOldMove &&
12266             commentList[cmailOldMove] != NULL &&
12267             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12268                    "Black offers a draw" : "White offers a draw")) {
12269 #ifdef NOTDEF
12270             AppendComment(cmailOldMove, "Draw declined", TRUE);
12271             DisplayComment(cmailOldMove - 1, "Draw declined");
12272 #endif /*NOTDEF*/
12273         } else {
12274             DisplayError(_("There is no pending offer on this move"), 0);
12275         }
12276     } else {
12277         /* Not used for offers from chess program */
12278     }
12279 }
12280
12281 void
12282 RematchEvent()
12283 {
12284     /* Issue ICS rematch command */
12285     if (appData.icsActive) {
12286         SendToICS(ics_prefix);
12287         SendToICS("rematch\n");
12288     }
12289 }
12290
12291 void
12292 CallFlagEvent()
12293 {
12294     /* Call your opponent's flag (claim a win on time) */
12295     if (appData.icsActive) {
12296         SendToICS(ics_prefix);
12297         SendToICS("flag\n");
12298     } else {
12299         switch (gameMode) {
12300           default:
12301             return;
12302           case MachinePlaysWhite:
12303             if (whiteFlag) {
12304                 if (blackFlag)
12305                   GameEnds(GameIsDrawn, "Both players ran out of time",
12306                            GE_PLAYER);
12307                 else
12308                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12309             } else {
12310                 DisplayError(_("Your opponent is not out of time"), 0);
12311             }
12312             break;
12313           case MachinePlaysBlack:
12314             if (blackFlag) {
12315                 if (whiteFlag)
12316                   GameEnds(GameIsDrawn, "Both players ran out of time",
12317                            GE_PLAYER);
12318                 else
12319                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12320             } else {
12321                 DisplayError(_("Your opponent is not out of time"), 0);
12322             }
12323             break;
12324         }
12325     }
12326 }
12327
12328 void
12329 DrawEvent()
12330 {
12331     /* Offer draw or accept pending draw offer from opponent */
12332     
12333     if (appData.icsActive) {
12334         /* Note: tournament rules require draw offers to be
12335            made after you make your move but before you punch
12336            your clock.  Currently ICS doesn't let you do that;
12337            instead, you immediately punch your clock after making
12338            a move, but you can offer a draw at any time. */
12339         
12340         SendToICS(ics_prefix);
12341         SendToICS("draw\n");
12342         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12343     } else if (cmailMsgLoaded) {
12344         if (currentMove == cmailOldMove &&
12345             commentList[cmailOldMove] != NULL &&
12346             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12347                    "Black offers a draw" : "White offers a draw")) {
12348             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12349             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12350         } else if (currentMove == cmailOldMove + 1) {
12351             char *offer = WhiteOnMove(cmailOldMove) ?
12352               "White offers a draw" : "Black offers a draw";
12353             AppendComment(currentMove, offer, TRUE);
12354             DisplayComment(currentMove - 1, offer);
12355             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12356         } else {
12357             DisplayError(_("You must make your move before offering a draw"), 0);
12358             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12359         }
12360     } else if (first.offeredDraw) {
12361         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12362     } else {
12363         if (first.sendDrawOffers) {
12364             SendToProgram("draw\n", &first);
12365             userOfferedDraw = TRUE;
12366         }
12367     }
12368 }
12369
12370 void
12371 AdjournEvent()
12372 {
12373     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12374     
12375     if (appData.icsActive) {
12376         SendToICS(ics_prefix);
12377         SendToICS("adjourn\n");
12378     } else {
12379         /* Currently GNU Chess doesn't offer or accept Adjourns */
12380     }
12381 }
12382
12383
12384 void
12385 AbortEvent()
12386 {
12387     /* Offer Abort or accept pending Abort offer from opponent */
12388     
12389     if (appData.icsActive) {
12390         SendToICS(ics_prefix);
12391         SendToICS("abort\n");
12392     } else {
12393         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12394     }
12395 }
12396
12397 void
12398 ResignEvent()
12399 {
12400     /* Resign.  You can do this even if it's not your turn. */
12401     
12402     if (appData.icsActive) {
12403         SendToICS(ics_prefix);
12404         SendToICS("resign\n");
12405     } else {
12406         switch (gameMode) {
12407           case MachinePlaysWhite:
12408             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12409             break;
12410           case MachinePlaysBlack:
12411             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12412             break;
12413           case EditGame:
12414             if (cmailMsgLoaded) {
12415                 TruncateGame();
12416                 if (WhiteOnMove(cmailOldMove)) {
12417                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12418                 } else {
12419                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12420                 }
12421                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12422             }
12423             break;
12424           default:
12425             break;
12426         }
12427     }
12428 }
12429
12430
12431 void
12432 StopObservingEvent()
12433 {
12434     /* Stop observing current games */
12435     SendToICS(ics_prefix);
12436     SendToICS("unobserve\n");
12437 }
12438
12439 void
12440 StopExaminingEvent()
12441 {
12442     /* Stop observing current game */
12443     SendToICS(ics_prefix);
12444     SendToICS("unexamine\n");
12445 }
12446
12447 void
12448 ForwardInner(target)
12449      int target;
12450 {
12451     int limit;
12452
12453     if (appData.debugMode)
12454         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12455                 target, currentMove, forwardMostMove);
12456
12457     if (gameMode == EditPosition)
12458       return;
12459
12460     if (gameMode == PlayFromGameFile && !pausing)
12461       PauseEvent();
12462     
12463     if (gameMode == IcsExamining && pausing)
12464       limit = pauseExamForwardMostMove;
12465     else
12466       limit = forwardMostMove;
12467     
12468     if (target > limit) target = limit;
12469
12470     if (target > 0 && moveList[target - 1][0]) {
12471         int fromX, fromY, toX, toY;
12472         toX = moveList[target - 1][2] - AAA;
12473         toY = moveList[target - 1][3] - ONE;
12474         if (moveList[target - 1][1] == '@') {
12475             if (appData.highlightLastMove) {
12476                 SetHighlights(-1, -1, toX, toY);
12477             }
12478         } else {
12479             fromX = moveList[target - 1][0] - AAA;
12480             fromY = moveList[target - 1][1] - ONE;
12481             if (target == currentMove + 1) {
12482                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12483             }
12484             if (appData.highlightLastMove) {
12485                 SetHighlights(fromX, fromY, toX, toY);
12486             }
12487         }
12488     }
12489     if (gameMode == EditGame || gameMode == AnalyzeMode || 
12490         gameMode == Training || gameMode == PlayFromGameFile || 
12491         gameMode == AnalyzeFile) {
12492         while (currentMove < target) {
12493             SendMoveToProgram(currentMove++, &first);
12494         }
12495     } else {
12496         currentMove = target;
12497     }
12498     
12499     if (gameMode == EditGame || gameMode == EndOfGame) {
12500         whiteTimeRemaining = timeRemaining[0][currentMove];
12501         blackTimeRemaining = timeRemaining[1][currentMove];
12502     }
12503     DisplayBothClocks();
12504     DisplayMove(currentMove - 1);
12505     DrawPosition(FALSE, boards[currentMove]);
12506     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12507     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12508         DisplayComment(currentMove - 1, commentList[currentMove]);
12509     }
12510 }
12511
12512
12513 void
12514 ForwardEvent()
12515 {
12516     if (gameMode == IcsExamining && !pausing) {
12517         SendToICS(ics_prefix);
12518         SendToICS("forward\n");
12519     } else {
12520         ForwardInner(currentMove + 1);
12521     }
12522 }
12523
12524 void
12525 ToEndEvent()
12526 {
12527     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12528         /* to optimze, we temporarily turn off analysis mode while we feed
12529          * the remaining moves to the engine. Otherwise we get analysis output
12530          * after each move.
12531          */ 
12532         if (first.analysisSupport) {
12533           SendToProgram("exit\nforce\n", &first);
12534           first.analyzing = FALSE;
12535         }
12536     }
12537         
12538     if (gameMode == IcsExamining && !pausing) {
12539         SendToICS(ics_prefix);
12540         SendToICS("forward 999999\n");
12541     } else {
12542         ForwardInner(forwardMostMove);
12543     }
12544
12545     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12546         /* we have fed all the moves, so reactivate analysis mode */
12547         SendToProgram("analyze\n", &first);
12548         first.analyzing = TRUE;
12549         /*first.maybeThinking = TRUE;*/
12550         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12551     }
12552 }
12553
12554 void
12555 BackwardInner(target)
12556      int target;
12557 {
12558     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12559
12560     if (appData.debugMode)
12561         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12562                 target, currentMove, forwardMostMove);
12563
12564     if (gameMode == EditPosition) return;
12565     if (currentMove <= backwardMostMove) {
12566         ClearHighlights();
12567         DrawPosition(full_redraw, boards[currentMove]);
12568         return;
12569     }
12570     if (gameMode == PlayFromGameFile && !pausing)
12571       PauseEvent();
12572     
12573     if (moveList[target][0]) {
12574         int fromX, fromY, toX, toY;
12575         toX = moveList[target][2] - AAA;
12576         toY = moveList[target][3] - ONE;
12577         if (moveList[target][1] == '@') {
12578             if (appData.highlightLastMove) {
12579                 SetHighlights(-1, -1, toX, toY);
12580             }
12581         } else {
12582             fromX = moveList[target][0] - AAA;
12583             fromY = moveList[target][1] - ONE;
12584             if (target == currentMove - 1) {
12585                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12586             }
12587             if (appData.highlightLastMove) {
12588                 SetHighlights(fromX, fromY, toX, toY);
12589             }
12590         }
12591     }
12592     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12593         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12594         while (currentMove > target) {
12595             SendToProgram("undo\n", &first);
12596             currentMove--;
12597         }
12598     } else {
12599         currentMove = target;
12600     }
12601     
12602     if (gameMode == EditGame || gameMode == EndOfGame) {
12603         whiteTimeRemaining = timeRemaining[0][currentMove];
12604         blackTimeRemaining = timeRemaining[1][currentMove];
12605     }
12606     DisplayBothClocks();
12607     DisplayMove(currentMove - 1);
12608     DrawPosition(full_redraw, boards[currentMove]);
12609     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12610     // [HGM] PV info: routine tests if comment empty
12611     DisplayComment(currentMove - 1, commentList[currentMove]);
12612 }
12613
12614 void
12615 BackwardEvent()
12616 {
12617     if (gameMode == IcsExamining && !pausing) {
12618         SendToICS(ics_prefix);
12619         SendToICS("backward\n");
12620     } else {
12621         BackwardInner(currentMove - 1);
12622     }
12623 }
12624
12625 void
12626 ToStartEvent()
12627 {
12628     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12629         /* to optimize, we temporarily turn off analysis mode while we undo
12630          * all the moves. Otherwise we get analysis output after each undo.
12631          */ 
12632         if (first.analysisSupport) {
12633           SendToProgram("exit\nforce\n", &first);
12634           first.analyzing = FALSE;
12635         }
12636     }
12637
12638     if (gameMode == IcsExamining && !pausing) {
12639         SendToICS(ics_prefix);
12640         SendToICS("backward 999999\n");
12641     } else {
12642         BackwardInner(backwardMostMove);
12643     }
12644
12645     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12646         /* we have fed all the moves, so reactivate analysis mode */
12647         SendToProgram("analyze\n", &first);
12648         first.analyzing = TRUE;
12649         /*first.maybeThinking = TRUE;*/
12650         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12651     }
12652 }
12653
12654 void
12655 ToNrEvent(int to)
12656 {
12657   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12658   if (to >= forwardMostMove) to = forwardMostMove;
12659   if (to <= backwardMostMove) to = backwardMostMove;
12660   if (to < currentMove) {
12661     BackwardInner(to);
12662   } else {
12663     ForwardInner(to);
12664   }
12665 }
12666
12667 void
12668 RevertEvent()
12669 {
12670     if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12671         return;
12672     }
12673     if (gameMode != IcsExamining) {
12674         DisplayError(_("You are not examining a game"), 0);
12675         return;
12676     }
12677     if (pausing) {
12678         DisplayError(_("You can't revert while pausing"), 0);
12679         return;
12680     }
12681     SendToICS(ics_prefix);
12682     SendToICS("revert\n");
12683 }
12684
12685 void
12686 RetractMoveEvent()
12687 {
12688     switch (gameMode) {
12689       case MachinePlaysWhite:
12690       case MachinePlaysBlack:
12691         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12692             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12693             return;
12694         }
12695         if (forwardMostMove < 2) return;
12696         currentMove = forwardMostMove = forwardMostMove - 2;
12697         whiteTimeRemaining = timeRemaining[0][currentMove];
12698         blackTimeRemaining = timeRemaining[1][currentMove];
12699         DisplayBothClocks();
12700         DisplayMove(currentMove - 1);
12701         ClearHighlights();/*!! could figure this out*/
12702         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12703         SendToProgram("remove\n", &first);
12704         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12705         break;
12706
12707       case BeginningOfGame:
12708       default:
12709         break;
12710
12711       case IcsPlayingWhite:
12712       case IcsPlayingBlack:
12713         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12714             SendToICS(ics_prefix);
12715             SendToICS("takeback 2\n");
12716         } else {
12717             SendToICS(ics_prefix);
12718             SendToICS("takeback 1\n");
12719         }
12720         break;
12721     }
12722 }
12723
12724 void
12725 MoveNowEvent()
12726 {
12727     ChessProgramState *cps;
12728
12729     switch (gameMode) {
12730       case MachinePlaysWhite:
12731         if (!WhiteOnMove(forwardMostMove)) {
12732             DisplayError(_("It is your turn"), 0);
12733             return;
12734         }
12735         cps = &first;
12736         break;
12737       case MachinePlaysBlack:
12738         if (WhiteOnMove(forwardMostMove)) {
12739             DisplayError(_("It is your turn"), 0);
12740             return;
12741         }
12742         cps = &first;
12743         break;
12744       case TwoMachinesPlay:
12745         if (WhiteOnMove(forwardMostMove) ==
12746             (first.twoMachinesColor[0] == 'w')) {
12747             cps = &first;
12748         } else {
12749             cps = &second;
12750         }
12751         break;
12752       case BeginningOfGame:
12753       default:
12754         return;
12755     }
12756     SendToProgram("?\n", cps);
12757 }
12758
12759 void
12760 TruncateGameEvent()
12761 {
12762     EditGameEvent();
12763     if (gameMode != EditGame) return;
12764     TruncateGame();
12765 }
12766
12767 void
12768 TruncateGame()
12769 {
12770     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12771     if (forwardMostMove > currentMove) {
12772         if (gameInfo.resultDetails != NULL) {
12773             free(gameInfo.resultDetails);
12774             gameInfo.resultDetails = NULL;
12775             gameInfo.result = GameUnfinished;
12776         }
12777         forwardMostMove = currentMove;
12778         HistorySet(parseList, backwardMostMove, forwardMostMove,
12779                    currentMove-1);
12780     }
12781 }
12782
12783 void
12784 HintEvent()
12785 {
12786     if (appData.noChessProgram) return;
12787     switch (gameMode) {
12788       case MachinePlaysWhite:
12789         if (WhiteOnMove(forwardMostMove)) {
12790             DisplayError(_("Wait until your turn"), 0);
12791             return;
12792         }
12793         break;
12794       case BeginningOfGame:
12795       case MachinePlaysBlack:
12796         if (!WhiteOnMove(forwardMostMove)) {
12797             DisplayError(_("Wait until your turn"), 0);
12798             return;
12799         }
12800         break;
12801       default:
12802         DisplayError(_("No hint available"), 0);
12803         return;
12804     }
12805     SendToProgram("hint\n", &first);
12806     hintRequested = TRUE;
12807 }
12808
12809 void
12810 BookEvent()
12811 {
12812     if (appData.noChessProgram) return;
12813     switch (gameMode) {
12814       case MachinePlaysWhite:
12815         if (WhiteOnMove(forwardMostMove)) {
12816             DisplayError(_("Wait until your turn"), 0);
12817             return;
12818         }
12819         break;
12820       case BeginningOfGame:
12821       case MachinePlaysBlack:
12822         if (!WhiteOnMove(forwardMostMove)) {
12823             DisplayError(_("Wait until your turn"), 0);
12824             return;
12825         }
12826         break;
12827       case EditPosition:
12828         EditPositionDone(TRUE);
12829         break;
12830       case TwoMachinesPlay:
12831         return;
12832       default:
12833         break;
12834     }
12835     SendToProgram("bk\n", &first);
12836     bookOutput[0] = NULLCHAR;
12837     bookRequested = TRUE;
12838 }
12839
12840 void
12841 AboutGameEvent()
12842 {
12843     char *tags = PGNTags(&gameInfo);
12844     TagsPopUp(tags, CmailMsg());
12845     free(tags);
12846 }
12847
12848 /* end button procedures */
12849
12850 void
12851 PrintPosition(fp, move)
12852      FILE *fp;
12853      int move;
12854 {
12855     int i, j;
12856     
12857     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12858         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12859             char c = PieceToChar(boards[move][i][j]);
12860             fputc(c == 'x' ? '.' : c, fp);
12861             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12862         }
12863     }
12864     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12865       fprintf(fp, "white to play\n");
12866     else
12867       fprintf(fp, "black to play\n");
12868 }
12869
12870 void
12871 PrintOpponents(fp)
12872      FILE *fp;
12873 {
12874     if (gameInfo.white != NULL) {
12875         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12876     } else {
12877         fprintf(fp, "\n");
12878     }
12879 }
12880
12881 /* Find last component of program's own name, using some heuristics */
12882 void
12883 TidyProgramName(prog, host, buf)
12884      char *prog, *host, buf[MSG_SIZ];
12885 {
12886     char *p, *q;
12887     int local = (strcmp(host, "localhost") == 0);
12888     while (!local && (p = strchr(prog, ';')) != NULL) {
12889         p++;
12890         while (*p == ' ') p++;
12891         prog = p;
12892     }
12893     if (*prog == '"' || *prog == '\'') {
12894         q = strchr(prog + 1, *prog);
12895     } else {
12896         q = strchr(prog, ' ');
12897     }
12898     if (q == NULL) q = prog + strlen(prog);
12899     p = q;
12900     while (p >= prog && *p != '/' && *p != '\\') p--;
12901     p++;
12902     if(p == prog && *p == '"') p++;
12903     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12904     memcpy(buf, p, q - p);
12905     buf[q - p] = NULLCHAR;
12906     if (!local) {
12907         strcat(buf, "@");
12908         strcat(buf, host);
12909     }
12910 }
12911
12912 char *
12913 TimeControlTagValue()
12914 {
12915     char buf[MSG_SIZ];
12916     if (!appData.clockMode) {
12917         strcpy(buf, "-");
12918     } else if (movesPerSession > 0) {
12919         sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12920     } else if (timeIncrement == 0) {
12921         sprintf(buf, "%ld", timeControl/1000);
12922     } else {
12923         sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12924     }
12925     return StrSave(buf);
12926 }
12927
12928 void
12929 SetGameInfo()
12930 {
12931     /* This routine is used only for certain modes */
12932     VariantClass v = gameInfo.variant;
12933     ChessMove r = GameUnfinished;
12934     char *p = NULL;
12935
12936     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12937         r = gameInfo.result; 
12938         p = gameInfo.resultDetails; 
12939         gameInfo.resultDetails = NULL;
12940     }
12941     ClearGameInfo(&gameInfo);
12942     gameInfo.variant = v;
12943
12944     switch (gameMode) {
12945       case MachinePlaysWhite:
12946         gameInfo.event = StrSave( appData.pgnEventHeader );
12947         gameInfo.site = StrSave(HostName());
12948         gameInfo.date = PGNDate();
12949         gameInfo.round = StrSave("-");
12950         gameInfo.white = StrSave(first.tidy);
12951         gameInfo.black = StrSave(UserName());
12952         gameInfo.timeControl = TimeControlTagValue();
12953         break;
12954
12955       case MachinePlaysBlack:
12956         gameInfo.event = StrSave( appData.pgnEventHeader );
12957         gameInfo.site = StrSave(HostName());
12958         gameInfo.date = PGNDate();
12959         gameInfo.round = StrSave("-");
12960         gameInfo.white = StrSave(UserName());
12961         gameInfo.black = StrSave(first.tidy);
12962         gameInfo.timeControl = TimeControlTagValue();
12963         break;
12964
12965       case TwoMachinesPlay:
12966         gameInfo.event = StrSave( appData.pgnEventHeader );
12967         gameInfo.site = StrSave(HostName());
12968         gameInfo.date = PGNDate();
12969         if (matchGame > 0) {
12970             char buf[MSG_SIZ];
12971             sprintf(buf, "%d", matchGame);
12972             gameInfo.round = StrSave(buf);
12973         } else {
12974             gameInfo.round = StrSave("-");
12975         }
12976         if (first.twoMachinesColor[0] == 'w') {
12977             gameInfo.white = StrSave(first.tidy);
12978             gameInfo.black = StrSave(second.tidy);
12979         } else {
12980             gameInfo.white = StrSave(second.tidy);
12981             gameInfo.black = StrSave(first.tidy);
12982         }
12983         gameInfo.timeControl = TimeControlTagValue();
12984         break;
12985
12986       case EditGame:
12987         gameInfo.event = StrSave("Edited game");
12988         gameInfo.site = StrSave(HostName());
12989         gameInfo.date = PGNDate();
12990         gameInfo.round = StrSave("-");
12991         gameInfo.white = StrSave("-");
12992         gameInfo.black = StrSave("-");
12993         gameInfo.result = r;
12994         gameInfo.resultDetails = p;
12995         break;
12996
12997       case EditPosition:
12998         gameInfo.event = StrSave("Edited position");
12999         gameInfo.site = StrSave(HostName());
13000         gameInfo.date = PGNDate();
13001         gameInfo.round = StrSave("-");
13002         gameInfo.white = StrSave("-");
13003         gameInfo.black = StrSave("-");
13004         break;
13005
13006       case IcsPlayingWhite:
13007       case IcsPlayingBlack:
13008       case IcsObserving:
13009       case IcsExamining:
13010         break;
13011
13012       case PlayFromGameFile:
13013         gameInfo.event = StrSave("Game from non-PGN file");
13014         gameInfo.site = StrSave(HostName());
13015         gameInfo.date = PGNDate();
13016         gameInfo.round = StrSave("-");
13017         gameInfo.white = StrSave("?");
13018         gameInfo.black = StrSave("?");
13019         break;
13020
13021       default:
13022         break;
13023     }
13024 }
13025
13026 void
13027 ReplaceComment(index, text)
13028      int index;
13029      char *text;
13030 {
13031     int len;
13032
13033     while (*text == '\n') text++;
13034     len = strlen(text);
13035     while (len > 0 && text[len - 1] == '\n') len--;
13036
13037     if (commentList[index] != NULL)
13038       free(commentList[index]);
13039
13040     if (len == 0) {
13041         commentList[index] = NULL;
13042         return;
13043     }
13044   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13045       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13046       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13047     commentList[index] = (char *) malloc(len + 2);
13048     strncpy(commentList[index], text, len);
13049     commentList[index][len] = '\n';
13050     commentList[index][len + 1] = NULLCHAR;
13051   } else { 
13052     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13053     char *p;
13054     commentList[index] = (char *) malloc(len + 6);
13055     strcpy(commentList[index], "{\n");
13056     strncpy(commentList[index]+2, text, len);
13057     commentList[index][len+2] = NULLCHAR;
13058     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13059     strcat(commentList[index], "\n}\n");
13060   }
13061 }
13062
13063 void
13064 CrushCRs(text)
13065      char *text;
13066 {
13067   char *p = text;
13068   char *q = text;
13069   char ch;
13070
13071   do {
13072     ch = *p++;
13073     if (ch == '\r') continue;
13074     *q++ = ch;
13075   } while (ch != '\0');
13076 }
13077
13078 void
13079 AppendComment(index, text, addBraces)
13080      int index;
13081      char *text;
13082      Boolean addBraces; // [HGM] braces: tells if we should add {}
13083 {
13084     int oldlen, len;
13085     char *old;
13086
13087 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13088     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13089
13090     CrushCRs(text);
13091     while (*text == '\n') text++;
13092     len = strlen(text);
13093     while (len > 0 && text[len - 1] == '\n') len--;
13094
13095     if (len == 0) return;
13096
13097     if (commentList[index] != NULL) {
13098         old = commentList[index];
13099         oldlen = strlen(old);
13100         while(commentList[index][oldlen-1] ==  '\n')
13101           commentList[index][--oldlen] = NULLCHAR;
13102         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13103         strcpy(commentList[index], old);
13104         free(old);
13105         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13106         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13107           if(addBraces) addBraces = FALSE; else { text++; len--; }
13108           while (*text == '\n') { text++; len--; }
13109           commentList[index][--oldlen] = NULLCHAR;
13110       }
13111         if(addBraces) strcat(commentList[index], "\n{\n");
13112         else          strcat(commentList[index], "\n");
13113         strcat(commentList[index], text);
13114         if(addBraces) strcat(commentList[index], "\n}\n");
13115         else          strcat(commentList[index], "\n");
13116     } else {
13117         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13118         if(addBraces)
13119              strcpy(commentList[index], "{\n");
13120         else commentList[index][0] = NULLCHAR;
13121         strcat(commentList[index], text);
13122         strcat(commentList[index], "\n");
13123         if(addBraces) strcat(commentList[index], "}\n");
13124     }
13125 }
13126
13127 static char * FindStr( char * text, char * sub_text )
13128 {
13129     char * result = strstr( text, sub_text );
13130
13131     if( result != NULL ) {
13132         result += strlen( sub_text );
13133     }
13134
13135     return result;
13136 }
13137
13138 /* [AS] Try to extract PV info from PGN comment */
13139 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13140 char *GetInfoFromComment( int index, char * text )
13141 {
13142     char * sep = text;
13143
13144     if( text != NULL && index > 0 ) {
13145         int score = 0;
13146         int depth = 0;
13147         int time = -1, sec = 0, deci;
13148         char * s_eval = FindStr( text, "[%eval " );
13149         char * s_emt = FindStr( text, "[%emt " );
13150
13151         if( s_eval != NULL || s_emt != NULL ) {
13152             /* New style */
13153             char delim;
13154
13155             if( s_eval != NULL ) {
13156                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13157                     return text;
13158                 }
13159
13160                 if( delim != ']' ) {
13161                     return text;
13162                 }
13163             }
13164
13165             if( s_emt != NULL ) {
13166             }
13167                 return text;
13168         }
13169         else {
13170             /* We expect something like: [+|-]nnn.nn/dd */
13171             int score_lo = 0;
13172
13173             if(*text != '{') return text; // [HGM] braces: must be normal comment
13174
13175             sep = strchr( text, '/' );
13176             if( sep == NULL || sep < (text+4) ) {
13177                 return text;
13178             }
13179
13180             time = -1; sec = -1; deci = -1;
13181             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13182                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13183                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13184                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13185                 return text;
13186             }
13187
13188             if( score_lo < 0 || score_lo >= 100 ) {
13189                 return text;
13190             }
13191
13192             if(sec >= 0) time = 600*time + 10*sec; else
13193             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13194
13195             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13196
13197             /* [HGM] PV time: now locate end of PV info */
13198             while( *++sep >= '0' && *sep <= '9'); // strip depth
13199             if(time >= 0)
13200             while( *++sep >= '0' && *sep <= '9'); // strip time
13201             if(sec >= 0)
13202             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13203             if(deci >= 0)
13204             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13205             while(*sep == ' ') sep++;
13206         }
13207
13208         if( depth <= 0 ) {
13209             return text;
13210         }
13211
13212         if( time < 0 ) {
13213             time = -1;
13214         }
13215
13216         pvInfoList[index-1].depth = depth;
13217         pvInfoList[index-1].score = score;
13218         pvInfoList[index-1].time  = 10*time; // centi-sec
13219         if(*sep == '}') *sep = 0; else *--sep = '{';
13220     }
13221     return sep;
13222 }
13223
13224 void
13225 SendToProgram(message, cps)
13226      char *message;
13227      ChessProgramState *cps;
13228 {
13229     int count, outCount, error;
13230     char buf[MSG_SIZ];
13231
13232     if (cps->pr == NULL) return;
13233     Attention(cps);
13234     
13235     if (appData.debugMode) {
13236         TimeMark now;
13237         GetTimeMark(&now);
13238         fprintf(debugFP, "%ld >%-6s: %s", 
13239                 SubtractTimeMarks(&now, &programStartTime),
13240                 cps->which, message);
13241     }
13242     
13243     count = strlen(message);
13244     outCount = OutputToProcess(cps->pr, message, count, &error);
13245     if (outCount < count && !exiting 
13246                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13247         sprintf(buf, _("Error writing to %s chess program"), cps->which);
13248         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13249             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13250                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13251                 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13252             } else {
13253                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13254             }
13255             gameInfo.resultDetails = StrSave(buf);
13256         }
13257         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13258     }
13259 }
13260
13261 void
13262 ReceiveFromProgram(isr, closure, message, count, error)
13263      InputSourceRef isr;
13264      VOIDSTAR closure;
13265      char *message;
13266      int count;
13267      int error;
13268 {
13269     char *end_str;
13270     char buf[MSG_SIZ];
13271     ChessProgramState *cps = (ChessProgramState *)closure;
13272
13273     if (isr != cps->isr) return; /* Killed intentionally */
13274     if (count <= 0) {
13275         if (count == 0) {
13276             sprintf(buf,
13277                     _("Error: %s chess program (%s) exited unexpectedly"),
13278                     cps->which, cps->program);
13279         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13280                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13281                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13282                     sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13283                 } else {
13284                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13285                 }
13286                 gameInfo.resultDetails = StrSave(buf);
13287             }
13288             RemoveInputSource(cps->isr);
13289             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13290         } else {
13291             sprintf(buf,
13292                     _("Error reading from %s chess program (%s)"),
13293                     cps->which, cps->program);
13294             RemoveInputSource(cps->isr);
13295
13296             /* [AS] Program is misbehaving badly... kill it */
13297             if( count == -2 ) {
13298                 DestroyChildProcess( cps->pr, 9 );
13299                 cps->pr = NoProc;
13300             }
13301
13302             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13303         }
13304         return;
13305     }
13306     
13307     if ((end_str = strchr(message, '\r')) != NULL)
13308       *end_str = NULLCHAR;
13309     if ((end_str = strchr(message, '\n')) != NULL)
13310       *end_str = NULLCHAR;
13311     
13312     if (appData.debugMode) {
13313         TimeMark now; int print = 1;
13314         char *quote = ""; char c; int i;
13315
13316         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13317                 char start = message[0];
13318                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13319                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && 
13320                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13321                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13322                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13323                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13324                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13325                    sscanf(message, "pong %c", &c)!=1   && start != '#')
13326                         { quote = "# "; print = (appData.engineComments == 2); }
13327                 message[0] = start; // restore original message
13328         }
13329         if(print) {
13330                 GetTimeMark(&now);
13331                 fprintf(debugFP, "%ld <%-6s: %s%s\n", 
13332                         SubtractTimeMarks(&now, &programStartTime), cps->which, 
13333                         quote,
13334                         message);
13335         }
13336     }
13337
13338     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13339     if (appData.icsEngineAnalyze) {
13340         if (strstr(message, "whisper") != NULL ||
13341              strstr(message, "kibitz") != NULL || 
13342             strstr(message, "tellics") != NULL) return;
13343     }
13344
13345     HandleMachineMove(message, cps);
13346 }
13347
13348
13349 void
13350 SendTimeControl(cps, mps, tc, inc, sd, st)
13351      ChessProgramState *cps;
13352      int mps, inc, sd, st;
13353      long tc;
13354 {
13355     char buf[MSG_SIZ];
13356     int seconds;
13357
13358     if( timeControl_2 > 0 ) {
13359         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13360             tc = timeControl_2;
13361         }
13362     }
13363     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13364     inc /= cps->timeOdds;
13365     st  /= cps->timeOdds;
13366
13367     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13368
13369     if (st > 0) {
13370       /* Set exact time per move, normally using st command */
13371       if (cps->stKludge) {
13372         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13373         seconds = st % 60;
13374         if (seconds == 0) {
13375           sprintf(buf, "level 1 %d\n", st/60);
13376         } else {
13377           sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13378         }
13379       } else {
13380         sprintf(buf, "st %d\n", st);
13381       }
13382     } else {
13383       /* Set conventional or incremental time control, using level command */
13384       if (seconds == 0) {
13385         /* Note old gnuchess bug -- minutes:seconds used to not work.
13386            Fixed in later versions, but still avoid :seconds
13387            when seconds is 0. */
13388         sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13389       } else {
13390         sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13391                 seconds, inc/1000);
13392       }
13393     }
13394     SendToProgram(buf, cps);
13395
13396     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13397     /* Orthogonally, limit search to given depth */
13398     if (sd > 0) {
13399       if (cps->sdKludge) {
13400         sprintf(buf, "depth\n%d\n", sd);
13401       } else {
13402         sprintf(buf, "sd %d\n", sd);
13403       }
13404       SendToProgram(buf, cps);
13405     }
13406
13407     if(cps->nps > 0) { /* [HGM] nps */
13408         if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13409         else {
13410                 sprintf(buf, "nps %d\n", cps->nps);
13411               SendToProgram(buf, cps);
13412         }
13413     }
13414 }
13415
13416 ChessProgramState *WhitePlayer()
13417 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13418 {
13419     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || 
13420        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13421         return &second;
13422     return &first;
13423 }
13424
13425 void
13426 SendTimeRemaining(cps, machineWhite)
13427      ChessProgramState *cps;
13428      int /*boolean*/ machineWhite;
13429 {
13430     char message[MSG_SIZ];
13431     long time, otime;
13432
13433     /* Note: this routine must be called when the clocks are stopped
13434        or when they have *just* been set or switched; otherwise
13435        it will be off by the time since the current tick started.
13436     */
13437     if (machineWhite) {
13438         time = whiteTimeRemaining / 10;
13439         otime = blackTimeRemaining / 10;
13440     } else {
13441         time = blackTimeRemaining / 10;
13442         otime = whiteTimeRemaining / 10;
13443     }
13444     /* [HGM] translate opponent's time by time-odds factor */
13445     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13446     if (appData.debugMode) {
13447         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13448     }
13449
13450     if (time <= 0) time = 1;
13451     if (otime <= 0) otime = 1;
13452     
13453     sprintf(message, "time %ld\n", time);
13454     SendToProgram(message, cps);
13455
13456     sprintf(message, "otim %ld\n", otime);
13457     SendToProgram(message, cps);
13458 }
13459
13460 int
13461 BoolFeature(p, name, loc, cps)
13462      char **p;
13463      char *name;
13464      int *loc;
13465      ChessProgramState *cps;
13466 {
13467   char buf[MSG_SIZ];
13468   int len = strlen(name);
13469   int val;
13470   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13471     (*p) += len + 1;
13472     sscanf(*p, "%d", &val);
13473     *loc = (val != 0);
13474     while (**p && **p != ' ') (*p)++;
13475     sprintf(buf, "accepted %s\n", name);
13476     SendToProgram(buf, cps);
13477     return TRUE;
13478   }
13479   return FALSE;
13480 }
13481
13482 int
13483 IntFeature(p, name, loc, cps)
13484      char **p;
13485      char *name;
13486      int *loc;
13487      ChessProgramState *cps;
13488 {
13489   char buf[MSG_SIZ];
13490   int len = strlen(name);
13491   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13492     (*p) += len + 1;
13493     sscanf(*p, "%d", loc);
13494     while (**p && **p != ' ') (*p)++;
13495     sprintf(buf, "accepted %s\n", name);
13496     SendToProgram(buf, cps);
13497     return TRUE;
13498   }
13499   return FALSE;
13500 }
13501
13502 int
13503 StringFeature(p, name, loc, cps)
13504      char **p;
13505      char *name;
13506      char loc[];
13507      ChessProgramState *cps;
13508 {
13509   char buf[MSG_SIZ];
13510   int len = strlen(name);
13511   if (strncmp((*p), name, len) == 0
13512       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13513     (*p) += len + 2;
13514     sscanf(*p, "%[^\"]", loc);
13515     while (**p && **p != '\"') (*p)++;
13516     if (**p == '\"') (*p)++;
13517     sprintf(buf, "accepted %s\n", name);
13518     SendToProgram(buf, cps);
13519     return TRUE;
13520   }
13521   return FALSE;
13522 }
13523
13524 int 
13525 ParseOption(Option *opt, ChessProgramState *cps)
13526 // [HGM] options: process the string that defines an engine option, and determine
13527 // name, type, default value, and allowed value range
13528 {
13529         char *p, *q, buf[MSG_SIZ];
13530         int n, min = (-1)<<31, max = 1<<31, def;
13531
13532         if(p = strstr(opt->name, " -spin ")) {
13533             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13534             if(max < min) max = min; // enforce consistency
13535             if(def < min) def = min;
13536             if(def > max) def = max;
13537             opt->value = def;
13538             opt->min = min;
13539             opt->max = max;
13540             opt->type = Spin;
13541         } else if((p = strstr(opt->name, " -slider "))) {
13542             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13543             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13544             if(max < min) max = min; // enforce consistency
13545             if(def < min) def = min;
13546             if(def > max) def = max;
13547             opt->value = def;
13548             opt->min = min;
13549             opt->max = max;
13550             opt->type = Spin; // Slider;
13551         } else if((p = strstr(opt->name, " -string "))) {
13552             opt->textValue = p+9;
13553             opt->type = TextBox;
13554         } else if((p = strstr(opt->name, " -file "))) {
13555             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13556             opt->textValue = p+7;
13557             opt->type = TextBox; // FileName;
13558         } else if((p = strstr(opt->name, " -path "))) {
13559             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13560             opt->textValue = p+7;
13561             opt->type = TextBox; // PathName;
13562         } else if(p = strstr(opt->name, " -check ")) {
13563             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13564             opt->value = (def != 0);
13565             opt->type = CheckBox;
13566         } else if(p = strstr(opt->name, " -combo ")) {
13567             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13568             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13569             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13570             opt->value = n = 0;
13571             while(q = StrStr(q, " /// ")) {
13572                 n++; *q = 0;    // count choices, and null-terminate each of them
13573                 q += 5;
13574                 if(*q == '*') { // remember default, which is marked with * prefix
13575                     q++;
13576                     opt->value = n;
13577                 }
13578                 cps->comboList[cps->comboCnt++] = q;
13579             }
13580             cps->comboList[cps->comboCnt++] = NULL;
13581             opt->max = n + 1;
13582             opt->type = ComboBox;
13583         } else if(p = strstr(opt->name, " -button")) {
13584             opt->type = Button;
13585         } else if(p = strstr(opt->name, " -save")) {
13586             opt->type = SaveButton;
13587         } else return FALSE;
13588         *p = 0; // terminate option name
13589         // now look if the command-line options define a setting for this engine option.
13590         if(cps->optionSettings && cps->optionSettings[0])
13591             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13592         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13593                 sprintf(buf, "option %s", p);
13594                 if(p = strstr(buf, ",")) *p = 0;
13595                 strcat(buf, "\n");
13596                 SendToProgram(buf, cps);
13597         }
13598         return TRUE;
13599 }
13600
13601 void
13602 FeatureDone(cps, val)
13603      ChessProgramState* cps;
13604      int val;
13605 {
13606   DelayedEventCallback cb = GetDelayedEvent();
13607   if ((cb == InitBackEnd3 && cps == &first) ||
13608       (cb == TwoMachinesEventIfReady && cps == &second)) {
13609     CancelDelayedEvent();
13610     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13611   }
13612   cps->initDone = val;
13613 }
13614
13615 /* Parse feature command from engine */
13616 void
13617 ParseFeatures(args, cps)
13618      char* args;
13619      ChessProgramState *cps;  
13620 {
13621   char *p = args;
13622   char *q;
13623   int val;
13624   char buf[MSG_SIZ];
13625
13626   for (;;) {
13627     while (*p == ' ') p++;
13628     if (*p == NULLCHAR) return;
13629
13630     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13631     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;    
13632     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;    
13633     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;    
13634     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;    
13635     if (BoolFeature(&p, "reuse", &val, cps)) {
13636       /* Engine can disable reuse, but can't enable it if user said no */
13637       if (!val) cps->reuse = FALSE;
13638       continue;
13639     }
13640     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13641     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13642       if (gameMode == TwoMachinesPlay) {
13643         DisplayTwoMachinesTitle();
13644       } else {
13645         DisplayTitle("");
13646       }
13647       continue;
13648     }
13649     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13650     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13651     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13652     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13653     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13654     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13655     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13656     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13657     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13658     if (IntFeature(&p, "done", &val, cps)) {
13659       FeatureDone(cps, val);
13660       continue;
13661     }
13662     /* Added by Tord: */
13663     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13664     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13665     /* End of additions by Tord */
13666
13667     /* [HGM] added features: */
13668     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13669     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13670     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13671     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13672     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13673     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13674     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13675         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13676             sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13677             SendToProgram(buf, cps);
13678             continue;
13679         }
13680         if(cps->nrOptions >= MAX_OPTIONS) {
13681             cps->nrOptions--;
13682             sprintf(buf, "%s engine has too many options\n", cps->which);
13683             DisplayError(buf, 0);
13684         }
13685         continue;
13686     }
13687     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13688     /* End of additions by HGM */
13689
13690     /* unknown feature: complain and skip */
13691     q = p;
13692     while (*q && *q != '=') q++;
13693     sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13694     SendToProgram(buf, cps);
13695     p = q;
13696     if (*p == '=') {
13697       p++;
13698       if (*p == '\"') {
13699         p++;
13700         while (*p && *p != '\"') p++;
13701         if (*p == '\"') p++;
13702       } else {
13703         while (*p && *p != ' ') p++;
13704       }
13705     }
13706   }
13707
13708 }
13709
13710 void
13711 PeriodicUpdatesEvent(newState)
13712      int newState;
13713 {
13714     if (newState == appData.periodicUpdates)
13715       return;
13716
13717     appData.periodicUpdates=newState;
13718
13719     /* Display type changes, so update it now */
13720 //    DisplayAnalysis();
13721
13722     /* Get the ball rolling again... */
13723     if (newState) {
13724         AnalysisPeriodicEvent(1);
13725         StartAnalysisClock();
13726     }
13727 }
13728
13729 void
13730 PonderNextMoveEvent(newState)
13731      int newState;
13732 {
13733     if (newState == appData.ponderNextMove) return;
13734     if (gameMode == EditPosition) EditPositionDone(TRUE);
13735     if (newState) {
13736         SendToProgram("hard\n", &first);
13737         if (gameMode == TwoMachinesPlay) {
13738             SendToProgram("hard\n", &second);
13739         }
13740     } else {
13741         SendToProgram("easy\n", &first);
13742         thinkOutput[0] = NULLCHAR;
13743         if (gameMode == TwoMachinesPlay) {
13744             SendToProgram("easy\n", &second);
13745         }
13746     }
13747     appData.ponderNextMove = newState;
13748 }
13749
13750 void
13751 NewSettingEvent(option, command, value)
13752      char *command;
13753      int option, value;
13754 {
13755     char buf[MSG_SIZ];
13756
13757     if (gameMode == EditPosition) EditPositionDone(TRUE);
13758     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13759     SendToProgram(buf, &first);
13760     if (gameMode == TwoMachinesPlay) {
13761         SendToProgram(buf, &second);
13762     }
13763 }
13764
13765 void
13766 ShowThinkingEvent()
13767 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13768 {
13769     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13770     int newState = appData.showThinking
13771         // [HGM] thinking: other features now need thinking output as well
13772         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13773     
13774     if (oldState == newState) return;
13775     oldState = newState;
13776     if (gameMode == EditPosition) EditPositionDone(TRUE);
13777     if (oldState) {
13778         SendToProgram("post\n", &first);
13779         if (gameMode == TwoMachinesPlay) {
13780             SendToProgram("post\n", &second);
13781         }
13782     } else {
13783         SendToProgram("nopost\n", &first);
13784         thinkOutput[0] = NULLCHAR;
13785         if (gameMode == TwoMachinesPlay) {
13786             SendToProgram("nopost\n", &second);
13787         }
13788     }
13789 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13790 }
13791
13792 void
13793 AskQuestionEvent(title, question, replyPrefix, which)
13794      char *title; char *question; char *replyPrefix; char *which;
13795 {
13796   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13797   if (pr == NoProc) return;
13798   AskQuestion(title, question, replyPrefix, pr);
13799 }
13800
13801 void
13802 DisplayMove(moveNumber)
13803      int moveNumber;
13804 {
13805     char message[MSG_SIZ];
13806     char res[MSG_SIZ];
13807     char cpThinkOutput[MSG_SIZ];
13808
13809     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13810     
13811     if (moveNumber == forwardMostMove - 1 || 
13812         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13813
13814         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13815
13816         if (strchr(cpThinkOutput, '\n')) {
13817             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13818         }
13819     } else {
13820         *cpThinkOutput = NULLCHAR;
13821     }
13822
13823     /* [AS] Hide thinking from human user */
13824     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13825         *cpThinkOutput = NULLCHAR;
13826         if( thinkOutput[0] != NULLCHAR ) {
13827             int i;
13828
13829             for( i=0; i<=hiddenThinkOutputState; i++ ) {
13830                 cpThinkOutput[i] = '.';
13831             }
13832             cpThinkOutput[i] = NULLCHAR;
13833             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13834         }
13835     }
13836
13837     if (moveNumber == forwardMostMove - 1 &&
13838         gameInfo.resultDetails != NULL) {
13839         if (gameInfo.resultDetails[0] == NULLCHAR) {
13840             sprintf(res, " %s", PGNResult(gameInfo.result));
13841         } else {
13842             sprintf(res, " {%s} %s",
13843                     gameInfo.resultDetails, PGNResult(gameInfo.result));
13844         }
13845     } else {
13846         res[0] = NULLCHAR;
13847     }
13848
13849     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13850         DisplayMessage(res, cpThinkOutput);
13851     } else {
13852         sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13853                 WhiteOnMove(moveNumber) ? " " : ".. ",
13854                 parseList[moveNumber], res);
13855         DisplayMessage(message, cpThinkOutput);
13856     }
13857 }
13858
13859 void
13860 DisplayComment(moveNumber, text)
13861      int moveNumber;
13862      char *text;
13863 {
13864     char title[MSG_SIZ];
13865     char buf[8000]; // comment can be long!
13866     int score, depth;
13867     
13868     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13869       strcpy(title, "Comment");
13870     } else {
13871       sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13872               WhiteOnMove(moveNumber) ? " " : ".. ",
13873               parseList[moveNumber]);
13874     }
13875     // [HGM] PV info: display PV info together with (or as) comment
13876     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13877       if(text == NULL) text = "";                                           
13878       score = pvInfoList[moveNumber].score;
13879       sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13880               depth, (pvInfoList[moveNumber].time+50)/100, text);
13881       text = buf;
13882     }
13883     if (text != NULL && (appData.autoDisplayComment || commentUp))
13884         CommentPopUp(title, text);
13885 }
13886
13887 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13888  * might be busy thinking or pondering.  It can be omitted if your
13889  * gnuchess is configured to stop thinking immediately on any user
13890  * input.  However, that gnuchess feature depends on the FIONREAD
13891  * ioctl, which does not work properly on some flavors of Unix.
13892  */
13893 void
13894 Attention(cps)
13895      ChessProgramState *cps;
13896 {
13897 #if ATTENTION
13898     if (!cps->useSigint) return;
13899     if (appData.noChessProgram || (cps->pr == NoProc)) return;
13900     switch (gameMode) {
13901       case MachinePlaysWhite:
13902       case MachinePlaysBlack:
13903       case TwoMachinesPlay:
13904       case IcsPlayingWhite:
13905       case IcsPlayingBlack:
13906       case AnalyzeMode:
13907       case AnalyzeFile:
13908         /* Skip if we know it isn't thinking */
13909         if (!cps->maybeThinking) return;
13910         if (appData.debugMode)
13911           fprintf(debugFP, "Interrupting %s\n", cps->which);
13912         InterruptChildProcess(cps->pr);
13913         cps->maybeThinking = FALSE;
13914         break;
13915       default:
13916         break;
13917     }
13918 #endif /*ATTENTION*/
13919 }
13920
13921 int
13922 CheckFlags()
13923 {
13924     if (whiteTimeRemaining <= 0) {
13925         if (!whiteFlag) {
13926             whiteFlag = TRUE;
13927             if (appData.icsActive) {
13928                 if (appData.autoCallFlag &&
13929                     gameMode == IcsPlayingBlack && !blackFlag) {
13930                   SendToICS(ics_prefix);
13931                   SendToICS("flag\n");
13932                 }
13933             } else {
13934                 if (blackFlag) {
13935                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13936                 } else {
13937                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13938                     if (appData.autoCallFlag) {
13939                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13940                         return TRUE;
13941                     }
13942                 }
13943             }
13944         }
13945     }
13946     if (blackTimeRemaining <= 0) {
13947         if (!blackFlag) {
13948             blackFlag = TRUE;
13949             if (appData.icsActive) {
13950                 if (appData.autoCallFlag &&
13951                     gameMode == IcsPlayingWhite && !whiteFlag) {
13952                   SendToICS(ics_prefix);
13953                   SendToICS("flag\n");
13954                 }
13955             } else {
13956                 if (whiteFlag) {
13957                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13958                 } else {
13959                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13960                     if (appData.autoCallFlag) {
13961                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13962                         return TRUE;
13963                     }
13964                 }
13965             }
13966         }
13967     }
13968     return FALSE;
13969 }
13970
13971 void
13972 CheckTimeControl()
13973 {
13974     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13975         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13976
13977     /*
13978      * add time to clocks when time control is achieved ([HGM] now also used for increment)
13979      */
13980     if ( !WhiteOnMove(forwardMostMove) )
13981         /* White made time control */
13982         whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13983         /* [HGM] time odds: correct new time quota for time odds! */
13984                                             / WhitePlayer()->timeOdds;
13985       else
13986         /* Black made time control */
13987         blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13988                                             / WhitePlayer()->other->timeOdds;
13989 }
13990
13991 void
13992 DisplayBothClocks()
13993 {
13994     int wom = gameMode == EditPosition ?
13995       !blackPlaysFirst : WhiteOnMove(currentMove);
13996     DisplayWhiteClock(whiteTimeRemaining, wom);
13997     DisplayBlackClock(blackTimeRemaining, !wom);
13998 }
13999
14000
14001 /* Timekeeping seems to be a portability nightmare.  I think everyone
14002    has ftime(), but I'm really not sure, so I'm including some ifdefs
14003    to use other calls if you don't.  Clocks will be less accurate if
14004    you have neither ftime nor gettimeofday.
14005 */
14006
14007 /* VS 2008 requires the #include outside of the function */
14008 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14009 #include <sys/timeb.h>
14010 #endif
14011
14012 /* Get the current time as a TimeMark */
14013 void
14014 GetTimeMark(tm)
14015      TimeMark *tm;
14016 {
14017 #if HAVE_GETTIMEOFDAY
14018
14019     struct timeval timeVal;
14020     struct timezone timeZone;
14021
14022     gettimeofday(&timeVal, &timeZone);
14023     tm->sec = (long) timeVal.tv_sec; 
14024     tm->ms = (int) (timeVal.tv_usec / 1000L);
14025
14026 #else /*!HAVE_GETTIMEOFDAY*/
14027 #if HAVE_FTIME
14028
14029 // include <sys/timeb.h> / moved to just above start of function
14030     struct timeb timeB;
14031
14032     ftime(&timeB);
14033     tm->sec = (long) timeB.time;
14034     tm->ms = (int) timeB.millitm;
14035
14036 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14037     tm->sec = (long) time(NULL);
14038     tm->ms = 0;
14039 #endif
14040 #endif
14041 }
14042
14043 /* Return the difference in milliseconds between two
14044    time marks.  We assume the difference will fit in a long!
14045 */
14046 long
14047 SubtractTimeMarks(tm2, tm1)
14048      TimeMark *tm2, *tm1;
14049 {
14050     return 1000L*(tm2->sec - tm1->sec) +
14051            (long) (tm2->ms - tm1->ms);
14052 }
14053
14054
14055 /*
14056  * Code to manage the game clocks.
14057  *
14058  * In tournament play, black starts the clock and then white makes a move.
14059  * We give the human user a slight advantage if he is playing white---the
14060  * clocks don't run until he makes his first move, so it takes zero time.
14061  * Also, we don't account for network lag, so we could get out of sync
14062  * with GNU Chess's clock -- but then, referees are always right.  
14063  */
14064
14065 static TimeMark tickStartTM;
14066 static long intendedTickLength;
14067
14068 long
14069 NextTickLength(timeRemaining)
14070      long timeRemaining;
14071 {
14072     long nominalTickLength, nextTickLength;
14073
14074     if (timeRemaining > 0L && timeRemaining <= 10000L)
14075       nominalTickLength = 100L;
14076     else
14077       nominalTickLength = 1000L;
14078     nextTickLength = timeRemaining % nominalTickLength;
14079     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14080
14081     return nextTickLength;
14082 }
14083
14084 /* Adjust clock one minute up or down */
14085 void
14086 AdjustClock(Boolean which, int dir)
14087 {
14088     if(which) blackTimeRemaining += 60000*dir;
14089     else      whiteTimeRemaining += 60000*dir;
14090     DisplayBothClocks();
14091 }
14092
14093 /* Stop clocks and reset to a fresh time control */
14094 void
14095 ResetClocks() 
14096 {
14097     (void) StopClockTimer();
14098     if (appData.icsActive) {
14099         whiteTimeRemaining = blackTimeRemaining = 0;
14100     } else if (searchTime) {
14101         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14102         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14103     } else { /* [HGM] correct new time quote for time odds */
14104         whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
14105         blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
14106     }
14107     if (whiteFlag || blackFlag) {
14108         DisplayTitle("");
14109         whiteFlag = blackFlag = FALSE;
14110     }
14111     DisplayBothClocks();
14112 }
14113
14114 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14115
14116 /* Decrement running clock by amount of time that has passed */
14117 void
14118 DecrementClocks()
14119 {
14120     long timeRemaining;
14121     long lastTickLength, fudge;
14122     TimeMark now;
14123
14124     if (!appData.clockMode) return;
14125     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14126         
14127     GetTimeMark(&now);
14128
14129     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14130
14131     /* Fudge if we woke up a little too soon */
14132     fudge = intendedTickLength - lastTickLength;
14133     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14134
14135     if (WhiteOnMove(forwardMostMove)) {
14136         if(whiteNPS >= 0) lastTickLength = 0;
14137         timeRemaining = whiteTimeRemaining -= lastTickLength;
14138         DisplayWhiteClock(whiteTimeRemaining - fudge,
14139                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14140     } else {
14141         if(blackNPS >= 0) lastTickLength = 0;
14142         timeRemaining = blackTimeRemaining -= lastTickLength;
14143         DisplayBlackClock(blackTimeRemaining - fudge,
14144                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14145     }
14146
14147     if (CheckFlags()) return;
14148         
14149     tickStartTM = now;
14150     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14151     StartClockTimer(intendedTickLength);
14152
14153     /* if the time remaining has fallen below the alarm threshold, sound the
14154      * alarm. if the alarm has sounded and (due to a takeback or time control
14155      * with increment) the time remaining has increased to a level above the
14156      * threshold, reset the alarm so it can sound again. 
14157      */
14158     
14159     if (appData.icsActive && appData.icsAlarm) {
14160
14161         /* make sure we are dealing with the user's clock */
14162         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14163                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14164            )) return;
14165
14166         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14167             alarmSounded = FALSE;
14168         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { 
14169             PlayAlarmSound();
14170             alarmSounded = TRUE;
14171         }
14172     }
14173 }
14174
14175
14176 /* A player has just moved, so stop the previously running
14177    clock and (if in clock mode) start the other one.
14178    We redisplay both clocks in case we're in ICS mode, because
14179    ICS gives us an update to both clocks after every move.
14180    Note that this routine is called *after* forwardMostMove
14181    is updated, so the last fractional tick must be subtracted
14182    from the color that is *not* on move now.
14183 */
14184 void
14185 SwitchClocks(int newMoveNr)
14186 {
14187     long lastTickLength;
14188     TimeMark now;
14189     int flagged = FALSE;
14190
14191     GetTimeMark(&now);
14192
14193     if (StopClockTimer() && appData.clockMode) {
14194         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14195         if (!WhiteOnMove(forwardMostMove)) {
14196             if(blackNPS >= 0) lastTickLength = 0;
14197             blackTimeRemaining -= lastTickLength;
14198            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14199 //         if(pvInfoList[forwardMostMove-1].time == -1)
14200                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14201                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14202         } else {
14203            if(whiteNPS >= 0) lastTickLength = 0;
14204            whiteTimeRemaining -= lastTickLength;
14205            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14206 //         if(pvInfoList[forwardMostMove-1].time == -1)
14207                  pvInfoList[forwardMostMove-1].time = 
14208                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14209         }
14210         flagged = CheckFlags();
14211     }
14212     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14213     CheckTimeControl();
14214
14215     if (flagged || !appData.clockMode) return;
14216
14217     switch (gameMode) {
14218       case MachinePlaysBlack:
14219       case MachinePlaysWhite:
14220       case BeginningOfGame:
14221         if (pausing) return;
14222         break;
14223
14224       case EditGame:
14225       case PlayFromGameFile:
14226       case IcsExamining:
14227         return;
14228
14229       default:
14230         break;
14231     }
14232
14233     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14234         if(WhiteOnMove(forwardMostMove))
14235              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14236         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14237     }
14238
14239     tickStartTM = now;
14240     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14241       whiteTimeRemaining : blackTimeRemaining);
14242     StartClockTimer(intendedTickLength);
14243 }
14244         
14245
14246 /* Stop both clocks */
14247 void
14248 StopClocks()
14249 {       
14250     long lastTickLength;
14251     TimeMark now;
14252
14253     if (!StopClockTimer()) return;
14254     if (!appData.clockMode) return;
14255
14256     GetTimeMark(&now);
14257
14258     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14259     if (WhiteOnMove(forwardMostMove)) {
14260         if(whiteNPS >= 0) lastTickLength = 0;
14261         whiteTimeRemaining -= lastTickLength;
14262         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14263     } else {
14264         if(blackNPS >= 0) lastTickLength = 0;
14265         blackTimeRemaining -= lastTickLength;
14266         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14267     }
14268     CheckFlags();
14269 }
14270         
14271 /* Start clock of player on move.  Time may have been reset, so
14272    if clock is already running, stop and restart it. */
14273 void
14274 StartClocks()
14275 {
14276     (void) StopClockTimer(); /* in case it was running already */
14277     DisplayBothClocks();
14278     if (CheckFlags()) return;
14279
14280     if (!appData.clockMode) return;
14281     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14282
14283     GetTimeMark(&tickStartTM);
14284     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14285       whiteTimeRemaining : blackTimeRemaining);
14286
14287    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14288     whiteNPS = blackNPS = -1; 
14289     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14290        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14291         whiteNPS = first.nps;
14292     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14293        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14294         blackNPS = first.nps;
14295     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14296         whiteNPS = second.nps;
14297     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14298         blackNPS = second.nps;
14299     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14300
14301     StartClockTimer(intendedTickLength);
14302 }
14303
14304 char *
14305 TimeString(ms)
14306      long ms;
14307 {
14308     long second, minute, hour, day;
14309     char *sign = "";
14310     static char buf[32];
14311     
14312     if (ms > 0 && ms <= 9900) {
14313       /* convert milliseconds to tenths, rounding up */
14314       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14315
14316       sprintf(buf, " %03.1f ", tenths/10.0);
14317       return buf;
14318     }
14319
14320     /* convert milliseconds to seconds, rounding up */
14321     /* use floating point to avoid strangeness of integer division
14322        with negative dividends on many machines */
14323     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14324
14325     if (second < 0) {
14326         sign = "-";
14327         second = -second;
14328     }
14329     
14330     day = second / (60 * 60 * 24);
14331     second = second % (60 * 60 * 24);
14332     hour = second / (60 * 60);
14333     second = second % (60 * 60);
14334     minute = second / 60;
14335     second = second % 60;
14336     
14337     if (day > 0)
14338       sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14339               sign, day, hour, minute, second);
14340     else if (hour > 0)
14341       sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14342     else
14343       sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14344     
14345     return buf;
14346 }
14347
14348
14349 /*
14350  * This is necessary because some C libraries aren't ANSI C compliant yet.
14351  */
14352 char *
14353 StrStr(string, match)
14354      char *string, *match;
14355 {
14356     int i, length;
14357     
14358     length = strlen(match);
14359     
14360     for (i = strlen(string) - length; i >= 0; i--, string++)
14361       if (!strncmp(match, string, length))
14362         return string;
14363     
14364     return NULL;
14365 }
14366
14367 char *
14368 StrCaseStr(string, match)
14369      char *string, *match;
14370 {
14371     int i, j, length;
14372     
14373     length = strlen(match);
14374     
14375     for (i = strlen(string) - length; i >= 0; i--, string++) {
14376         for (j = 0; j < length; j++) {
14377             if (ToLower(match[j]) != ToLower(string[j]))
14378               break;
14379         }
14380         if (j == length) return string;
14381     }
14382
14383     return NULL;
14384 }
14385
14386 #ifndef _amigados
14387 int
14388 StrCaseCmp(s1, s2)
14389      char *s1, *s2;
14390 {
14391     char c1, c2;
14392     
14393     for (;;) {
14394         c1 = ToLower(*s1++);
14395         c2 = ToLower(*s2++);
14396         if (c1 > c2) return 1;
14397         if (c1 < c2) return -1;
14398         if (c1 == NULLCHAR) return 0;
14399     }
14400 }
14401
14402
14403 int
14404 ToLower(c)
14405      int c;
14406 {
14407     return isupper(c) ? tolower(c) : c;
14408 }
14409
14410
14411 int
14412 ToUpper(c)
14413      int c;
14414 {
14415     return islower(c) ? toupper(c) : c;
14416 }
14417 #endif /* !_amigados    */
14418
14419 char *
14420 StrSave(s)
14421      char *s;
14422 {
14423     char *ret;
14424
14425     if ((ret = (char *) malloc(strlen(s) + 1))) {
14426         strcpy(ret, s);
14427     }
14428     return ret;
14429 }
14430
14431 char *
14432 StrSavePtr(s, savePtr)
14433      char *s, **savePtr;
14434 {
14435     if (*savePtr) {
14436         free(*savePtr);
14437     }
14438     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14439         strcpy(*savePtr, s);
14440     }
14441     return(*savePtr);
14442 }
14443
14444 char *
14445 PGNDate()
14446 {
14447     time_t clock;
14448     struct tm *tm;
14449     char buf[MSG_SIZ];
14450
14451     clock = time((time_t *)NULL);
14452     tm = localtime(&clock);
14453     sprintf(buf, "%04d.%02d.%02d",
14454             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14455     return StrSave(buf);
14456 }
14457
14458
14459 char *
14460 PositionToFEN(move, overrideCastling)
14461      int move;
14462      char *overrideCastling;
14463 {
14464     int i, j, fromX, fromY, toX, toY;
14465     int whiteToPlay;
14466     char buf[128];
14467     char *p, *q;
14468     int emptycount;
14469     ChessSquare piece;
14470
14471     whiteToPlay = (gameMode == EditPosition) ?
14472       !blackPlaysFirst : (move % 2 == 0);
14473     p = buf;
14474
14475     /* Piece placement data */
14476     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14477         emptycount = 0;
14478         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14479             if (boards[move][i][j] == EmptySquare) {
14480                 emptycount++;
14481             } else { ChessSquare piece = boards[move][i][j];
14482                 if (emptycount > 0) {
14483                     if(emptycount<10) /* [HGM] can be >= 10 */
14484                         *p++ = '0' + emptycount;
14485                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14486                     emptycount = 0;
14487                 }
14488                 if(PieceToChar(piece) == '+') {
14489                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14490                     *p++ = '+';
14491                     piece = (ChessSquare)(DEMOTED piece);
14492                 } 
14493                 *p++ = PieceToChar(piece);
14494                 if(p[-1] == '~') {
14495                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14496                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14497                     *p++ = '~';
14498                 }
14499             }
14500         }
14501         if (emptycount > 0) {
14502             if(emptycount<10) /* [HGM] can be >= 10 */
14503                 *p++ = '0' + emptycount;
14504             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14505             emptycount = 0;
14506         }
14507         *p++ = '/';
14508     }
14509     *(p - 1) = ' ';
14510
14511     /* [HGM] print Crazyhouse or Shogi holdings */
14512     if( gameInfo.holdingsWidth ) {
14513         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14514         q = p;
14515         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14516             piece = boards[move][i][BOARD_WIDTH-1];
14517             if( piece != EmptySquare )
14518               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14519                   *p++ = PieceToChar(piece);
14520         }
14521         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14522             piece = boards[move][BOARD_HEIGHT-i-1][0];
14523             if( piece != EmptySquare )
14524               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14525                   *p++ = PieceToChar(piece);
14526         }
14527
14528         if( q == p ) *p++ = '-';
14529         *p++ = ']';
14530         *p++ = ' ';
14531     }
14532
14533     /* Active color */
14534     *p++ = whiteToPlay ? 'w' : 'b';
14535     *p++ = ' ';
14536
14537   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14538     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14539   } else {
14540   if(nrCastlingRights) {
14541      q = p;
14542      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14543        /* [HGM] write directly from rights */
14544            if(boards[move][CASTLING][2] != NoRights &&
14545               boards[move][CASTLING][0] != NoRights   )
14546                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14547            if(boards[move][CASTLING][2] != NoRights &&
14548               boards[move][CASTLING][1] != NoRights   )
14549                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14550            if(boards[move][CASTLING][5] != NoRights &&
14551               boards[move][CASTLING][3] != NoRights   )
14552                 *p++ = boards[move][CASTLING][3] + AAA;
14553            if(boards[move][CASTLING][5] != NoRights &&
14554               boards[move][CASTLING][4] != NoRights   )
14555                 *p++ = boards[move][CASTLING][4] + AAA;
14556      } else {
14557
14558         /* [HGM] write true castling rights */
14559         if( nrCastlingRights == 6 ) {
14560             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14561                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14562             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14563                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14564             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14565                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14566             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14567                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14568         }
14569      }
14570      if (q == p) *p++ = '-'; /* No castling rights */
14571      *p++ = ' ';
14572   }
14573
14574   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14575      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14576     /* En passant target square */
14577     if (move > backwardMostMove) {
14578         fromX = moveList[move - 1][0] - AAA;
14579         fromY = moveList[move - 1][1] - ONE;
14580         toX = moveList[move - 1][2] - AAA;
14581         toY = moveList[move - 1][3] - ONE;
14582         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14583             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14584             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14585             fromX == toX) {
14586             /* 2-square pawn move just happened */
14587             *p++ = toX + AAA;
14588             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14589         } else {
14590             *p++ = '-';
14591         }
14592     } else if(move == backwardMostMove) {
14593         // [HGM] perhaps we should always do it like this, and forget the above?
14594         if((signed char)boards[move][EP_STATUS] >= 0) {
14595             *p++ = boards[move][EP_STATUS] + AAA;
14596             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14597         } else {
14598             *p++ = '-';
14599         }
14600     } else {
14601         *p++ = '-';
14602     }
14603     *p++ = ' ';
14604   }
14605   }
14606
14607     /* [HGM] find reversible plies */
14608     {   int i = 0, j=move;
14609
14610         if (appData.debugMode) { int k;
14611             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14612             for(k=backwardMostMove; k<=forwardMostMove; k++)
14613                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14614
14615         }
14616
14617         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14618         if( j == backwardMostMove ) i += initialRulePlies;
14619         sprintf(p, "%d ", i);
14620         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14621     }
14622     /* Fullmove number */
14623     sprintf(p, "%d", (move / 2) + 1);
14624     
14625     return StrSave(buf);
14626 }
14627
14628 Boolean
14629 ParseFEN(board, blackPlaysFirst, fen)
14630     Board board;
14631      int *blackPlaysFirst;
14632      char *fen;
14633 {
14634     int i, j;
14635     char *p;
14636     int emptycount;
14637     ChessSquare piece;
14638
14639     p = fen;
14640
14641     /* [HGM] by default clear Crazyhouse holdings, if present */
14642     if(gameInfo.holdingsWidth) {
14643        for(i=0; i<BOARD_HEIGHT; i++) {
14644            board[i][0]             = EmptySquare; /* black holdings */
14645            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14646            board[i][1]             = (ChessSquare) 0; /* black counts */
14647            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14648        }
14649     }
14650
14651     /* Piece placement data */
14652     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14653         j = 0;
14654         for (;;) {
14655             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14656                 if (*p == '/') p++;
14657                 emptycount = gameInfo.boardWidth - j;
14658                 while (emptycount--)
14659                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14660                 break;
14661 #if(BOARD_FILES >= 10)
14662             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14663                 p++; emptycount=10;
14664                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14665                 while (emptycount--)
14666                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14667 #endif
14668             } else if (isdigit(*p)) {
14669                 emptycount = *p++ - '0';
14670                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14671                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14672                 while (emptycount--)
14673                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14674             } else if (*p == '+' || isalpha(*p)) {
14675                 if (j >= gameInfo.boardWidth) return FALSE;
14676                 if(*p=='+') {
14677                     piece = CharToPiece(*++p);
14678                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14679                     piece = (ChessSquare) (PROMOTED piece ); p++;
14680                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14681                 } else piece = CharToPiece(*p++);
14682
14683                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14684                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14685                     piece = (ChessSquare) (PROMOTED piece);
14686                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14687                     p++;
14688                 }
14689                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14690             } else {
14691                 return FALSE;
14692             }
14693         }
14694     }
14695     while (*p == '/' || *p == ' ') p++;
14696
14697     /* [HGM] look for Crazyhouse holdings here */
14698     while(*p==' ') p++;
14699     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14700         if(*p == '[') p++;
14701         if(*p == '-' ) *p++; /* empty holdings */ else {
14702             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14703             /* if we would allow FEN reading to set board size, we would   */
14704             /* have to add holdings and shift the board read so far here   */
14705             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14706                 *p++;
14707                 if((int) piece >= (int) BlackPawn ) {
14708                     i = (int)piece - (int)BlackPawn;
14709                     i = PieceToNumber((ChessSquare)i);
14710                     if( i >= gameInfo.holdingsSize ) return FALSE;
14711                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14712                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14713                 } else {
14714                     i = (int)piece - (int)WhitePawn;
14715                     i = PieceToNumber((ChessSquare)i);
14716                     if( i >= gameInfo.holdingsSize ) return FALSE;
14717                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14718                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14719                 }
14720             }
14721         }
14722         if(*p == ']') *p++;
14723     }
14724
14725     while(*p == ' ') p++;
14726
14727     /* Active color */
14728     switch (*p++) {
14729       case 'w':
14730         *blackPlaysFirst = FALSE;
14731         break;
14732       case 'b': 
14733         *blackPlaysFirst = TRUE;
14734         break;
14735       default:
14736         return FALSE;
14737     }
14738
14739     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14740     /* return the extra info in global variiables             */
14741
14742     /* set defaults in case FEN is incomplete */
14743     board[EP_STATUS] = EP_UNKNOWN;
14744     for(i=0; i<nrCastlingRights; i++ ) {
14745         board[CASTLING][i] =
14746             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14747     }   /* assume possible unless obviously impossible */
14748     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14749     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14750     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14751                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14752     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14753     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14754     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14755                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14756     FENrulePlies = 0;
14757
14758     while(*p==' ') p++;
14759     if(nrCastlingRights) {
14760       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14761           /* castling indicator present, so default becomes no castlings */
14762           for(i=0; i<nrCastlingRights; i++ ) {
14763                  board[CASTLING][i] = NoRights;
14764           }
14765       }
14766       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14767              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14768              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14769              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14770         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14771
14772         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14773             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14774             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14775         }
14776         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14777             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14778         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14779                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14780         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14781                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14782         switch(c) {
14783           case'K':
14784               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14785               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14786               board[CASTLING][2] = whiteKingFile;
14787               break;
14788           case'Q':
14789               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14790               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14791               board[CASTLING][2] = whiteKingFile;
14792               break;
14793           case'k':
14794               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14795               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14796               board[CASTLING][5] = blackKingFile;
14797               break;
14798           case'q':
14799               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14800               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14801               board[CASTLING][5] = blackKingFile;
14802           case '-':
14803               break;
14804           default: /* FRC castlings */
14805               if(c >= 'a') { /* black rights */
14806                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14807                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14808                   if(i == BOARD_RGHT) break;
14809                   board[CASTLING][5] = i;
14810                   c -= AAA;
14811                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
14812                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
14813                   if(c > i)
14814                       board[CASTLING][3] = c;
14815                   else
14816                       board[CASTLING][4] = c;
14817               } else { /* white rights */
14818                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14819                     if(board[0][i] == WhiteKing) break;
14820                   if(i == BOARD_RGHT) break;
14821                   board[CASTLING][2] = i;
14822                   c -= AAA - 'a' + 'A';
14823                   if(board[0][c] >= WhiteKing) break;
14824                   if(c > i)
14825                       board[CASTLING][0] = c;
14826                   else
14827                       board[CASTLING][1] = c;
14828               }
14829         }
14830       }
14831       for(i=0; i<nrCastlingRights; i++)
14832         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14833     if (appData.debugMode) {
14834         fprintf(debugFP, "FEN castling rights:");
14835         for(i=0; i<nrCastlingRights; i++)
14836         fprintf(debugFP, " %d", board[CASTLING][i]);
14837         fprintf(debugFP, "\n");
14838     }
14839
14840       while(*p==' ') p++;
14841     }
14842
14843     /* read e.p. field in games that know e.p. capture */
14844     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14845        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { 
14846       if(*p=='-') {
14847         p++; board[EP_STATUS] = EP_NONE;
14848       } else {
14849          char c = *p++ - AAA;
14850
14851          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14852          if(*p >= '0' && *p <='9') *p++;
14853          board[EP_STATUS] = c;
14854       }
14855     }
14856
14857
14858     if(sscanf(p, "%d", &i) == 1) {
14859         FENrulePlies = i; /* 50-move ply counter */
14860         /* (The move number is still ignored)    */
14861     }
14862
14863     return TRUE;
14864 }
14865       
14866 void
14867 EditPositionPasteFEN(char *fen)
14868 {
14869   if (fen != NULL) {
14870     Board initial_position;
14871
14872     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14873       DisplayError(_("Bad FEN position in clipboard"), 0);
14874       return ;
14875     } else {
14876       int savedBlackPlaysFirst = blackPlaysFirst;
14877       EditPositionEvent();
14878       blackPlaysFirst = savedBlackPlaysFirst;
14879       CopyBoard(boards[0], initial_position);
14880       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14881       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14882       DisplayBothClocks();
14883       DrawPosition(FALSE, boards[currentMove]);
14884     }
14885   }
14886 }
14887
14888 static char cseq[12] = "\\   ";
14889
14890 Boolean set_cont_sequence(char *new_seq)
14891 {
14892     int len;
14893     Boolean ret;
14894
14895     // handle bad attempts to set the sequence
14896         if (!new_seq)
14897                 return 0; // acceptable error - no debug
14898
14899     len = strlen(new_seq);
14900     ret = (len > 0) && (len < sizeof(cseq));
14901     if (ret)
14902         strcpy(cseq, new_seq);
14903     else if (appData.debugMode)
14904         fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14905     return ret;
14906 }
14907
14908 /*
14909     reformat a source message so words don't cross the width boundary.  internal
14910     newlines are not removed.  returns the wrapped size (no null character unless
14911     included in source message).  If dest is NULL, only calculate the size required
14912     for the dest buffer.  lp argument indicats line position upon entry, and it's
14913     passed back upon exit.
14914 */
14915 int wrap(char *dest, char *src, int count, int width, int *lp)
14916 {
14917     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14918
14919     cseq_len = strlen(cseq);
14920     old_line = line = *lp;
14921     ansi = len = clen = 0;
14922
14923     for (i=0; i < count; i++)
14924     {
14925         if (src[i] == '\033')
14926             ansi = 1;
14927
14928         // if we hit the width, back up
14929         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14930         {
14931             // store i & len in case the word is too long
14932             old_i = i, old_len = len;
14933
14934             // find the end of the last word
14935             while (i && src[i] != ' ' && src[i] != '\n')
14936             {
14937                 i--;
14938                 len--;
14939             }
14940
14941             // word too long?  restore i & len before splitting it
14942             if ((old_i-i+clen) >= width)
14943             {
14944                 i = old_i;
14945                 len = old_len;
14946             }
14947
14948             // extra space?
14949             if (i && src[i-1] == ' ')
14950                 len--;
14951
14952             if (src[i] != ' ' && src[i] != '\n')
14953             {
14954                 i--;
14955                 if (len)
14956                     len--;
14957             }
14958
14959             // now append the newline and continuation sequence
14960             if (dest)
14961                 dest[len] = '\n';
14962             len++;
14963             if (dest)
14964                 strncpy(dest+len, cseq, cseq_len);
14965             len += cseq_len;
14966             line = cseq_len;
14967             clen = cseq_len;
14968             continue;
14969         }
14970
14971         if (dest)
14972             dest[len] = src[i];
14973         len++;
14974         if (!ansi)
14975             line++;
14976         if (src[i] == '\n')
14977             line = 0;
14978         if (src[i] == 'm')
14979             ansi = 0;
14980     }
14981     if (dest && appData.debugMode)
14982     {
14983         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14984             count, width, line, len, *lp);
14985         show_bytes(debugFP, src, count);
14986         fprintf(debugFP, "\ndest: ");
14987         show_bytes(debugFP, dest, len);
14988         fprintf(debugFP, "\n");
14989     }
14990     *lp = dest ? line : old_line;
14991
14992     return len;
14993 }
14994
14995 // [HGM] vari: routines for shelving variations
14996
14997 void 
14998 PushTail(int firstMove, int lastMove)
14999 {
15000         int i, j, nrMoves = lastMove - firstMove;
15001
15002         if(appData.icsActive) { // only in local mode
15003                 forwardMostMove = currentMove; // mimic old ICS behavior
15004                 return;
15005         }
15006         if(storedGames >= MAX_VARIATIONS-1) return;
15007
15008         // push current tail of game on stack
15009         savedResult[storedGames] = gameInfo.result;
15010         savedDetails[storedGames] = gameInfo.resultDetails;
15011         gameInfo.resultDetails = NULL;
15012         savedFirst[storedGames] = firstMove;
15013         savedLast [storedGames] = lastMove;
15014         savedFramePtr[storedGames] = framePtr;
15015         framePtr -= nrMoves; // reserve space for the boards
15016         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15017             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15018             for(j=0; j<MOVE_LEN; j++)
15019                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15020             for(j=0; j<2*MOVE_LEN; j++)
15021                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15022             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15023             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15024             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15025             pvInfoList[firstMove+i-1].depth = 0;
15026             commentList[framePtr+i] = commentList[firstMove+i];
15027             commentList[firstMove+i] = NULL;
15028         }
15029
15030         storedGames++;
15031         forwardMostMove = currentMove; // truncte game so we can start variation
15032         if(storedGames == 1) GreyRevert(FALSE);
15033 }
15034
15035 Boolean
15036 PopTail(Boolean annotate)
15037 {
15038         int i, j, nrMoves;
15039         char buf[8000], moveBuf[20];
15040
15041         if(appData.icsActive) return FALSE; // only in local mode
15042         if(!storedGames) return FALSE; // sanity
15043
15044         storedGames--;
15045         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15046         nrMoves = savedLast[storedGames] - currentMove;
15047         if(annotate) {
15048                 int cnt = 10;
15049                 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
15050                 else strcpy(buf, "(");
15051                 for(i=currentMove; i<forwardMostMove; i++) {
15052                         if(WhiteOnMove(i))
15053                              sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
15054                         else sprintf(moveBuf, " %s", SavePart(parseList[i]));
15055                         strcat(buf, moveBuf);
15056                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15057                 }
15058                 strcat(buf, ")");
15059         }
15060         for(i=1; i<nrMoves; i++) { // copy last variation back
15061             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15062             for(j=0; j<MOVE_LEN; j++)
15063                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15064             for(j=0; j<2*MOVE_LEN; j++)
15065                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15066             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15067             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15068             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15069             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15070             commentList[currentMove+i] = commentList[framePtr+i];
15071             commentList[framePtr+i] = NULL;
15072         }
15073         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15074         framePtr = savedFramePtr[storedGames];
15075         gameInfo.result = savedResult[storedGames];
15076         if(gameInfo.resultDetails != NULL) {
15077             free(gameInfo.resultDetails);
15078       }
15079         gameInfo.resultDetails = savedDetails[storedGames];
15080         forwardMostMove = currentMove + nrMoves;
15081         if(storedGames == 0) GreyRevert(TRUE);
15082         return TRUE;
15083 }
15084
15085 void 
15086 CleanupTail()
15087 {       // remove all shelved variations
15088         int i;
15089         for(i=0; i<storedGames; i++) {
15090             if(savedDetails[i])
15091                 free(savedDetails[i]);
15092             savedDetails[i] = NULL;
15093         }
15094         for(i=framePtr; i<MAX_MOVES; i++) {
15095                 if(commentList[i]) free(commentList[i]);
15096                 commentList[i] = NULL;
15097         }
15098         framePtr = MAX_MOVES-1;
15099         storedGames = 0;
15100 }